7

When attempting to perform a center crop of an UIImage, I get the following results (left is the original image at 640*1136, and the right is the cropped image fitted to a square UIImageView at 320*320):

enter image description here turns to: enter image description here

I have tinkered quite a bit with the ratio element so that it could correctly detect the amount to trim: using the shorter side of of the image, constructing a ratio based on short_side/width_of_desired_rect, but it does not appear to be working in this case. Help appreciated!

- (UIImage *)squareImageWithImage:(UIImage *)image scaledToSize:(CGSize)newSize {
    double ratio;
    double delta;
    CGPoint offset;

    //make a new square size, that is the resized imaged width
    CGSize sz = CGSizeMake(newSize.width, newSize.width);

    //figure out if the picture is landscape or portrait, then
    //calculate scale factor and offset
    if (image.size.width > image.size.height) {
        ratio = newSize.width / image.size.width;
        delta = (ratio*image.size.width - ratio*image.size.height);
        offset = CGPointMake(delta/2, 0);
    } else {
        ratio = newSize.width / image.size.height;
        delta = (ratio*image.size.height - ratio*image.size.width);
        offset = CGPointMake(0, delta/2);
    }

    //make the final clipping rect based on the calculated values
    CGRect clipRect = CGRectMake(-offset.x, -offset.y,
                                 (ratio * image.size.width) + delta,
                                 (ratio * image.size.height) + delta);

    //for retina consideration
    if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
        UIGraphicsBeginImageContextWithOptions(sz, YES, 0.0);
    } else {
        UIGraphicsBeginImageContext(sz);
    }
    UIRectClip(clipRect);
    [image drawInRect:clipRect];
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    return newImage;
}
4
  • FYI - The scale property of UIScreen was added in iOS 4.0. There's no need to check for it anymore.
    – rmaddy
    Commented May 2, 2014 at 23:31
  • You are not cropping. You are redrawing the full image into a smaller area. Do a search on "ios crop image" and you will find plenty of ways to solve the problem.
    – rmaddy
    Commented May 2, 2014 at 23:33
  • Thanks @rmaddy - I think I am both cropping and redrawing the image (the squished image has 8 vertical square counts, while the original image had more than 11).
    – daspianist
    Commented May 2, 2014 at 23:38
  • You don't want to draw into the clipRect. You want to draw the image in its original aspect ratio such that only the part you want to keep is rendered within the clipRect.
    – rmaddy
    Commented May 2, 2014 at 23:40

3 Answers 3

41

Try this:

- (UIImage *)squareImageFromImage:(UIImage *)image scaledToSize:(CGFloat)newSize {
    CGAffineTransform scaleTransform;
    CGPoint origin;

    if (image.size.width > image.size.height) {
        CGFloat scaleRatio = newSize / image.size.height;
        scaleTransform = CGAffineTransformMakeScale(scaleRatio, scaleRatio);

        origin = CGPointMake(-(image.size.width - image.size.height) / 2.0f, 0);
    } else {
        CGFloat scaleRatio = newSize / image.size.width;
        scaleTransform = CGAffineTransformMakeScale(scaleRatio, scaleRatio);

        origin = CGPointMake(0, -(image.size.height - image.size.width) / 2.0f);
    }

    CGSize size = CGSizeMake(newSize, newSize);
    if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) {
        UIGraphicsBeginImageContextWithOptions(size, YES, 0);
    } else {
        UIGraphicsBeginImageContext(size);
    }

    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextConcatCTM(context, scaleTransform);

    [image drawAtPoint:origin];

    image = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    return image;
}
7
  • Thanks for answering @fumoboy007 - unfortunately it didn't work :/ The image blew up quite big and didn't properly crop. I also deleted the BOOL isLandscape since it wasn't used in the rest of the code.
    – daspianist
    Commented May 3, 2014 at 5:12
  • Hmmm… that's strange. Can I see some screenshots?
    – fumoboy007
    Commented May 3, 2014 at 5:39
  • Ah forgot to scale… hold on.
    – fumoboy007
    Commented May 3, 2014 at 5:50
  • This worked great, both front and back cameras. Thank you! I just added a little bit of change since newSize is a CGSize and not CGFloat.
    – daspianist
    Commented May 3, 2014 at 15:14
  • 1
    You're welcome. =) For letter boxing, yeah pretty much flip the scaling and cropping logic around and also draw a black rectangle (or whatever colour).
    – fumoboy007
    Commented May 3, 2014 at 15:33
2

Use this code to resize your image

// Returns image resized to the desired CGSize
- (UIImage *)imageWithImage:(UIImage *)image scaledToSize:(CGSize)newSize {
    UIGraphicsBeginImageContext(newSize);
    [image drawInRect:CGRectMake(0, 0, newSize.width, newSize.height)];
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return newImage;
}

Edit: you are basically redrawing here too. If you are reusing the same image over and over, it might be a good idea to call this once and save the result as a file. Load that instead of the original instead in later calls.

Edit 2: Okay so what you want to do is keep the aspect ratio while fitting the image into a square. The above code basically fits the image into a square by resizing it but it does not keep the aspect ratio. A way of also keeping the aspect ratio would be to find the factor by which you shrink the larger of the two dimensions of the image and then shrink the other dimension by the same factor. Let me know if you have trouble with it.

2
  • Thanks user2891327. While this works great while using the rear facing camera, when using the front facing camera the image still ends up squished. Strangely this seems to be the case with another set of code as well
    – daspianist
    Commented May 3, 2014 at 4:05
  • I added some screenshots in case it helps. This is what the camera captured (note, its in 9:16 aspect ratio) without resizing to square: i.imgur.com/88SaGO6.png, and this is after resizing to square: i.imgur.com/fL8TMWr.png
    – daspianist
    Commented May 3, 2014 at 4:19
0

Swift3 version of fumoboy007's code..

     func squareImage(img: UIImage, scaledToSize newSize: CGFloat) -> UIImage {
    var scaleTransform: CGAffineTransform
    var origin: CGPoint
    var image = img
    if image.size.width > image.size.height {
        let scaleRatio: CGFloat = newSize / image.size.height
        scaleTransform = CGAffineTransform(scaleX: scaleRatio, y: scaleRatio)
        origin = CGPoint(x: -(image.size.width - image.size.height) / 2.0, y: 0)
    }
    else {
        let scaleRatio: CGFloat = newSize / image.size.width
        scaleTransform = CGAffineTransform(scaleX: scaleRatio, y: scaleRatio)
        origin = CGPoint(x: 0, y: -(image.size.height - image.size.width) / 2.0)
    }
    let size = CGSize(width: newSize, height: newSize)
    if image.size.width > image.size.height {
        UIGraphicsBeginImageContextWithOptions(size, true, 0)
    }
    else {
        UIGraphicsBeginImageContext(size)
    }

    let context: CGContext? = UIGraphicsGetCurrentContext()
    context?.concatenate(scaleTransform)
    image.draw(at: origin)
    image = UIGraphicsGetImageFromCurrentImageContext()!
    UIGraphicsEndImageContext()
    return image
}

Not the answer you're looking for? Browse other questions tagged or ask your own question.