Search code examples
iosuiimagecrop

Finding the biggest centered square from a landscape or a portrait UIImage and scale it to a size


I need to find the biggest centered square from a portrait or a landscape image scaled to a size. E.g. if I get an image of size 1200x800 and I need to get the centered square down to size 300x300.


Solution

  • I found an answer on this question on stackoverflow which has been widely copied. However that answer is incorrect, so want to post the correct answer which is as follows:

    + (UIImage*) cropBiggestCenteredSquareImageFromImage:(UIImage*)image withSide:(CGFloat)side
    {
      // Get size of current image
      CGSize size = [image size];
      if( size.width == size.height && size.width == side){
        return image;
      }
    
      CGSize newSize = CGSizeMake(side, side);
      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.height / image.size.height;
        delta = ratio*(image.size.width - image.size.height);
        offset = CGPointMake(delta/2, 0);
      } else {
        ratio = newSize.width / image.size.width;
        delta = ratio*(image.size.height - 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),
                                   (ratio * image.size.height));
    
      //start a new context, with scale factor 0.0 so retina displays get
      //high quality image
      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;
    }
    

    Incorrect answer which I found earlier is as follows:

    + (UIImage*) cropBiggestCenteredSquareImageFromImage:(UIImage*)image withSide:(CGFloat)side
    {
      // Get size of current image
      CGSize size = [image size];
      if( size.width == size.height && size.width == side){
        return image;
      }
    
      CGSize newSize = CGSizeMake(side, side);
      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);
    
    
      //start a new context, with scale factor 0.0 so retina displays get
      //high quality image
      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;
    }
    

    The problem with this code is that it does not crop correctly.

    Both the codes can be tried on following image: https://s3.amazonaws.com/anandprakash/ImageWithPixelGrid.jpg

    Correct Algo generates following image on the above base url: https://s3.amazonaws.com/anandprakash/ScreenshotCorrectAlgo.png

    Wrong Algo generates following image on the above base url - notice the extra 50px on the width on each side. https://s3.amazonaws.com/anandprakash/ScreenshotWrongAlgo.png