resize and crop image centered

This method will do what you want and is a category of UIImage for ease of use. I went with resize then crop, you could switch the code around easily enough if you want crop then resize. The bounds checking in the function is purely illustrative. You might want to do something different, for example center the crop rect relative to the outputImage dimensions but this ought to get you close enough to make whatever other changes you need.

@implementation UIImage( resizeAndCropExample )

- (UIImage *) resizeToSize:(CGSize) newSize thenCropWithRect:(CGRect) cropRect {
    CGContextRef                context;
    CGImageRef                  imageRef;
    CGSize                      inputSize;
    UIImage                     *outputImage = nil;
    CGFloat                     scaleFactor, width;

    // resize, maintaining aspect ratio:

    inputSize = self.size;
    scaleFactor = newSize.height / inputSize.height;
    width = roundf( inputSize.width * scaleFactor );

    if ( width > newSize.width ) {
        scaleFactor = newSize.width / inputSize.width;
        newSize.height = roundf( inputSize.height * scaleFactor );
    } else {
        newSize.width = width;
    }

    UIGraphicsBeginImageContext( newSize );

    context = UIGraphicsGetCurrentContext();

    // added 2016.07.29, flip image vertically before drawing:
    CGContextSaveGState(context);
    CGContextTranslateCTM(context, 0, newSize.height);
    CGContextScaleCTM(context, 1, -1);
    CGContextDrawImage(context, CGRectMake(0, 0, newSize.width, newSize.height, self.CGImage);

//  // alternate way to draw
//  [self drawInRect: CGRectMake( 0, 0, newSize.width, newSize.height )];

    CGContextRestoreGState(context);

    outputImage = UIGraphicsGetImageFromCurrentImageContext();

    UIGraphicsEndImageContext();

    inputSize = newSize;

    // constrain crop rect to legitimate bounds
    if ( cropRect.origin.x >= inputSize.width || cropRect.origin.y >= inputSize.height ) return outputImage;
    if ( cropRect.origin.x + cropRect.size.width >= inputSize.width ) cropRect.size.width = inputSize.width - cropRect.origin.x;
    if ( cropRect.origin.y + cropRect.size.height >= inputSize.height ) cropRect.size.height = inputSize.height - cropRect.origin.y;

    // crop
    if ( ( imageRef = CGImageCreateWithImageInRect( outputImage.CGImage, cropRect ) ) ) {
        outputImage = [[[UIImage alloc] initWithCGImage: imageRef] autorelease];
        CGImageRelease( imageRef );
    }

    return outputImage;
}

@end

I have came across the same issue in one of my application and developed this piece of code:

+ (UIImage*)resizeImage:(UIImage*)image toFitInSize:(CGSize)toSize
{
    UIImage *result = image;
    CGSize sourceSize = image.size;
    CGSize targetSize = toSize;

    BOOL needsRedraw = NO;

    // Check if width of source image is greater than width of target image
    // Calculate the percentage of change in width required and update it in toSize accordingly.

    if (sourceSize.width > toSize.width) {

        CGFloat ratioChange = (sourceSize.width - toSize.width) * 100 / sourceSize.width;

        toSize.height = sourceSize.height - (sourceSize.height * ratioChange / 100);

        needsRedraw = YES;
    }

    // Now we need to make sure that if we chnage the height of image in same proportion
    // Calculate the percentage of change in width required and update it in target size variable.
    // Also we need to again change the height of the target image in the same proportion which we
    /// have calculated for the change.

    if (toSize.height < targetSize.height) {

        CGFloat ratioChange = (targetSize.height - toSize.height) * 100 / targetSize.height;

        toSize.height = targetSize.height;
        toSize.width = toSize.width + (toSize.width * ratioChange / 100);

        needsRedraw = YES;
    }

    // To redraw the image

    if (needsRedraw) {
        UIGraphicsBeginImageContext(toSize);
        [image drawInRect:CGRectMake(0.0, 0.0, toSize.width, toSize.height)];
        result = UIGraphicsGetImageFromCurrentImageContext();
        UIGraphicsEndImageContext();
    }

    // Return the result

    return result;
}

You can modify it according to your needs.


Had the same task for preview image in a gallery. For fixed Crop-Area (Image for SwiftUI and Canvas Rect for Kotiln) I want to crop central content of image - by maximum of on of the image's side. (see explanation below)

Here solutions for

Swift

func getImageCropped(srcImage : UIImage, sizeToCrop : CGSize) -> UIImage{

                let ratioImage = Double(srcImage.cgImage!.width )  / Double(srcImage.cgImage!.height )
                let ratioCrop  = Double(sizeToCrop.width)          / Double(sizeToCrop.height)

                let cropRect: CGRect = {
                    if(ratioCrop > 1.0){
                        // crop LAND  -> fit image HORIZONTALLY

                        let widthRatio = CGFloat(srcImage.cgImage!.width) / CGFloat(sizeToCrop.width)

                        var cropWidth  = Int(sizeToCrop.width  * widthRatio)
                        var cropHeight = Int(sizeToCrop.height * widthRatio)
                        var cropX      = 0
                        var cropY      = srcImage.cgImage!.height / 2 - cropHeight / 2

                        // [L1] [L2]        : OK

                        if(ratioImage > 1.0) {
                            // image LAND

                             // [L3]        : OK

                            if(cropHeight > srcImage.cgImage!.height) {

                                // [L4]     : Crop-Area exceeds Image-Area > change crop orientation to PORTrait

                                let heightRatio = CGFloat(srcImage.cgImage!.height) / CGFloat(sizeToCrop.height)

                                cropWidth  = Int(sizeToCrop.width  * heightRatio)
                                cropHeight = Int(sizeToCrop.height * heightRatio)
                                cropX      = srcImage.cgImage!.width / 2 - cropWidth / 2
                                cropY      = 0
                            }
                        }

                        return CGRect(x: cropX, y: cropY, width: cropWidth, height: cropHeight)
                    }
                    else if(ratioCrop < 1.0){
                        // crop PORT  -> fit image VERTICALLY

                        let heightRatio = CGFloat(srcImage.cgImage!.height) / CGFloat(sizeToCrop.height)

                        var cropWidth  = Int(sizeToCrop.width  * heightRatio)
                        var cropHeight = Int(sizeToCrop.height * heightRatio)
                        var cropX      = srcImage.cgImage!.width / 2 - cropWidth / 2
                        var cropY      = 0

                        // [P1] [P2]        : OK

                        if(ratioImage < 1.0) {
        //                  // image  PORT

                            // [P3]        : OK

                            if(cropWidth > srcImage.cgImage!.width) {

                                // [L4]     : Crop-Area exceeds Image-Area > change crop orientation to LANDscape

                                let widthRatio = CGFloat(srcImage.cgImage!.width) / CGFloat(sizeToCrop.width)

                                cropWidth  = Int(sizeToCrop.width  * widthRatio)
                                cropHeight = Int(sizeToCrop.height  * widthRatio)
                                cropX      = 0
                                cropY      = srcImage.cgImage!.height / 2 - cropHeight / 2
                            }
                        }
                        return CGRect(x: cropX, y: cropY, width: cropWidth, height: cropHeight)
                    }
                    else {
                        // CROP CENTER SQUARE

                        var fitSide = 0

                        var cropX = 0
                        var cropY = 0

                        if (ratioImage > 1.0){
                            // crop LAND  -> fit image HORIZONTALLY     !!!!!!
                            fitSide = srcImage.cgImage!.height
                            cropX = srcImage.cgImage!.width / 2 - fitSide / 2
                        }
                        else if (ratioImage < 1.0){
                            // crop PORT  -> fit image VERTICALLY
                            fitSide = srcImage.cgImage!.width
                            cropY = srcImage.cgImage!.height / 2 - fitSide / 2
                        }
                        else{
                            // ImageAre and GropArea are square both
                            fitSide = srcImage.cgImage!.width
                        }

                        return CGRect(x: cropX, y: cropY, width: fitSide, height: fitSide)
                    }

                }()

                let imageRef = srcImage.cgImage!.cropping(to: cropRect)

                let cropped : UIImage = UIImage(cgImage: imageRef!, scale: 0, orientation: srcImage.imageOrientation)

                return cropped

    }

and

Kotlin

data class RectCrop(val x: Int, val y: Int, val width: Int, val height: Int)    

fun getImageCroppedShort(srcBitmap: Bitmap, sizeToCrop: Size):Bitmap {



            val ratioImage = srcBitmap.width.toFloat()  / srcBitmap.height.toFloat()
            val ratioCrop  = sizeToCrop.width.toFloat() / sizeToCrop.height.toFloat()


    //    1. choose fit size
            val cropRect: RectCrop =

                if(ratioCrop > 1.0){
    //              crop  LAND

                    val widthRatio = srcBitmap.width.toFloat() / sizeToCrop.width.toFloat()

                    var cropWidth = (sizeToCrop.width  * widthRatio).toInt()
                    var cropHeight= (sizeToCrop.height * widthRatio).toInt()
                    var cropX          = 0
                    var cropY     = srcBitmap.height / 2 - cropHeight / 2

                    if(ratioImage > 1.0) {
    //                  image LAND

                        if(cropHeight > srcBitmap.height) {

                            val heightRatio = srcBitmap.height.toFloat() / sizeToCrop.height.toFloat()

                            cropWidth  = (sizeToCrop.width  * heightRatio).toInt()
                            cropHeight = (sizeToCrop.height * heightRatio).toInt()
                            cropX      = srcBitmap.width / 2 - cropWidth / 2
                            cropY      = 0
                        }
                    }

                    RectCrop(cropX, cropY, cropWidth, cropHeight)
                }
                else if(ratioCrop < 1.0){
    //              crop  PORT

                    val heightRatio = srcBitmap.height.toFloat() / sizeToCrop.height.toFloat()

                    var cropWidth = (sizeToCrop.width  * heightRatio).toInt()
                    var cropHeight= (sizeToCrop.height * heightRatio).toInt()
                    var cropX     = srcBitmap.width / 2 - cropWidth / 2
                    var cropY          = 0

                    if(ratioImage < 1.0) {
    //                  image  PORT

                        if(cropWidth > srcBitmap.width) {

                            val widthRatio = srcBitmap.width.toFloat() / sizeToCrop.width.toFloat()

                            cropWidth  = (sizeToCrop.width  * widthRatio).toInt()
                            cropHeight = (sizeToCrop.height * widthRatio).toInt()
                            cropX      = 0
                            cropY      = srcBitmap.height / 2 - cropHeight / 2
                        }
                    }

                    RectCrop(cropX, cropY, cropWidth, cropHeight)
                }
                else {
                    // CROP CENTER SQUARE

                    var fitSide = 0

                    var cropX = 0
                    var cropY = 0

                    if (ratioImage > 1.0){
                        fitSide = srcBitmap.height
                        cropX = srcBitmap.width/ 2 - fitSide / 2
                    }
                    else if (ratioImage < 1.0){
                        fitSide = srcBitmap.width
                        cropY = srcBitmap.height / 2 - fitSide / 2
                    }
                    else{
                        fitSide = srcBitmap.width
                    }

                    RectCrop(cropX, cropY, fitSide, fitSide)
                }

            return Bitmap.createBitmap(
                srcBitmap,
                cropRect.x,
                cropRect.y,
                cropRect.width,
                cropRect.height)
        }

An explanation for those who want to understand algorithm. The main idea - we should stretch a Crop-Area proportionally(!) until the biggest side of it fits image. But there is one unacceptable case (L4 and P4) when Crop-Area exceeds Image-Area. So here we have only one way - change fit direction and stretch Crop-Area to the other side

enter image description here

On Scheme I didn't centering of crop (for better understanding idea), but both of this solutions do this. Here result of getImageCropped:

Landscape crop Portrait crop Square crop

This SwiftUI code provides images above to test:

        var body: some View {

            // IMAGE LAND
            let ORIG_NAME = "image_land.jpg"
            let ORIG_W = 400.0
            let ORIG_H = 265.0

            //  > crop Land
            let cropW = 400.0
            let cropH = 200.0

            //  > crop Port
//            let cropW = 50.0
//            let cropH = 265.0

            //  > crop Center Square
//            let cropW = 265.0
//            let cropH = 265.0



            // IMAGE PORT
//            let ORIG_NAME = "image_port.jpg"
//            let ORIG_W = 350.0
//            let ORIG_H = 500.0

            //  > crop Land
//            let cropW = 350.0
//            let cropH = 410.0

            //  > crop Port
//            let cropW = 190.0
//            let cropH = 500.0

            //  > crop Center Square
//            let cropW = 350.0
//            let cropH = 350.0



            let imageOriginal = UIImage(named: ORIG_NAME)!
            let imageCropped  = self.getImageCroppedShort(srcImage: imageOriginal, sizeToCrop: CGSize(width: cropW, height: cropH))

            return VStack{
                HStack{
                    Text("ImageArea \nW:\(Int(ORIG_W)) \nH:\(Int(ORIG_H))").font(.body)
                    Text("CropArea \nW:\(Int(cropW)) \nH:\(Int(cropH))").font(.body)
                }
                ZStack{

                    Image(uiImage: imageOriginal)
                        .resizable()
                        .opacity(0.4)

                    Image(uiImage: imageCropped)
                        .resizable()
                        .frame(width: CGFloat(cropW), height: CGFloat(cropH))
                }
                .frame(width: CGFloat(ORIG_W), height: CGFloat(ORIG_H))
                .background(Color.black)
            }
        }

Kotlin solution works identically. Trust me)