Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Scaling, Resizing and Orienting Images to a Bounding Square in Swift

DZone's Guide to

Scaling, Resizing and Orienting Images to a Bounding Square in Swift

· Java Zone ·
Free Resource

Build vs Buy a Data Quality Solution: Which is Best for You? Gain insights on a hybrid approach. Download white paper now!


Earlier this year, I blogged about a small extension I wrote for UIImage for resizing images within a bounding square. Part of my current project, a rewrite of my Nodality application in Swift, requires similar functionality but I wanted to improve the scaling quality and handle images with different orientations (i.e. portrait and landscape).

My resizeToBoundingSquare() function accepts an image and numeric value that defines the bounding square's width and it returns a UIImage. The signature, therefore, is:


 func resizeToBoundingSquare(sourceImage: UIImage, #boundingSquareSideLength : CGFloat) -> UIImage

My first step was to use Lanczos resampling rather than simply relying on drawInRect() at the new size. Lanczos offers smooth resampling which should lead to a better quality image and it's available as one of the built in Core Image filters. 

The code to execute the filter and generate a properly sized CIImage is:

let imgScale = sourceImage.size.width < sourceImage.size.height ? boundingSquareSideLength / sourceImage.size.width : boundingSquareSideLength / sourceImage.size.height
    
    let scaleTransform = CIFilter(name: "CILanczosScaleTransform")
    scaleTransform.setValue(CIImage(image: sourceImage), forKey: "inputImage")
    scaleTransform.setValue(imgScale, forKey: "inputScale")
    scaleTransform.setValue(1.0, forKey: "inputAspectRatio")

    let outputImage = scaleTransform.valueForKey("outputImage") as CIImage

The CIImage is scaled but not square. To create a cropped and centred UIImage, I use the following code:

let context = CIContext(options: nil)
    
    let extent = outputImage.extent()
    let xOffset = Int(extent.width) > NodeConstants.imageWidth ? (extent.width - boundingSquareSideLength) / 2 : 0
    let yOffset = Int(extent.height) > NodeConstants.imageWidth ? (extent.height - boundingSquareSideLength) / 2 : 0
    

    let scaledImage = UIImage(CGImage: context.createCGImage(outputImage, fromRect: CGRect(x: xOffset, y: yOffset, width: boundingSquareSideLength, height: boundingSquareSideLength)))!

Now we have a scaled, square UIImage but there's one final issue. If the original source has been shot in landscape format, the image is rotated. So the final steps are to look at the source image's orientation and rotate accordingly. First, I define an angle to rotate by:

var angle = 0.0;
    
    if (sourceImage.imageOrientation == UIImageOrientation.Right)
    {
        angle = 90.0
    }
    else if (sourceImage.imageOrientation == UIImageOrientation.Left)
    {
        angle = -90.0
    }
    else if (sourceImage.imageOrientation == UIImageOrientation.Down)
    {
        angle = 180
    }
    else if (sourceImage.imageOrientation == UIImageOrientation.Up)
    {
        angle = 0.0

    }

Then I use a CGContext to draw the image. Because the rotation pivot point is at (0,0), I need to translate the context by half the bounding square width before rotating and do the drawAtPoint() at the opposite of that translation:

UIGraphicsBeginImageContext(CGSize(width: boundingSquareSideLength, height: boundingSquareSideLength))
    let cgContext = UIGraphicsGetCurrentContext()
    
    let offset = boundingSquareSideLength / 2.0
    
    CGContextTranslateCTM(cgContext, offset, offset)
    CGContextRotateCTM(cgContext, CGFloat(angle * M_PI / 180))
    

    scaledImage.drawAtPoint(CGPoint(x: -offset, y: -offset))

Finally, I get the UIImage from the context and release it:

let final = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext()
    

    return final


And the result is beautifully scaled images, centred and cropped to a square!

This source code isn't publicly available on GitHub, but this post does contain the full source code of resizeToBoundingSquare().

Finally, if you're wondering how Nodality is looking in Swift, here's a little demo video to whet your appetite:


Build vs Buy a Data Quality Solution: Which is Best for You? Maintaining high quality data is essential for operational efficiency, meaningful analytics and good long-term customer relationships. But, when dealing with multiple sources of data, data quality becomes complex, so you need to know when you should build a custom data quality tools effort over canned solutions. Download our whitepaper for more insights into a hybrid approach.

Topics:

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}