resize and crop image centered

Viewed 12471

so currently i'm trying to crop and resize a picture to fit it into a specific size without losing the ratio.

a small image to show what i mean:

alt text

i played a bit with vocaro's categories but they don't work with png's and have problems with gifs. also the image doesn't get cropped.

does anyone have a suggestion how to do this resizing the best way or probably have a link to an existing library/categorie/whatever?

thanks for all hints!

p.s.: does ios implement a "select a excerpt" so that i have the right ratio and only have to scale it?!

3 Answers

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)

Related