Image Crop / Rotate / Resize Handling in iOS

Crop, Rotate and Resize are the fundamental image operations. In app development we come to a situation when we have to do these operations. In this post, I will guide you how to use these operations in your iOS applications using XCode 8.0 and Swift 3.0.

Image Cropping:

Image cropping is a process of extracting some interested portion of an image. In this post we will create following method to crop an image:             

func crop(image:UIImage, cropRect:CGRect) -> UIImage? {
}

In this method `image` parameter is source image to be cropped and `cropRect` is a rectangle in which image will be cropped. This method will return UIImage object of cropped image.

First we will start from creating an image context for drawing environment

UIGraphicsBeginImageContextWithOptions(cropRect.size, false, image.scale)

Here we passed the size of resulting image as first parameter, second parameter indicates that the image is not opaque and third parameter specifies the image scale.

Now we will create a point which will translate the source image according to the origin of given cropping rectangle.

let origin = CGPoint(x: cropRect.origin.x * CGFloat(-1), y: cropRect.origin.y * CGFloat(-1))

Now we will draw the source image into current context at the translated point

image.draw(at: origin)

Now we will get the resulted image, close the image context and return the resulting UIImage object.

let result = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext();
       
return result

So the final method for image cropping will be:

func crop(image:UIImage, cropRect:CGRect) -> UIImage? {
    UIGraphicsBeginImageContextWithOptions(cropRect.size, false, image.scale)
    let origin = CGPoint(x: cropRect.origin.x * CGFloat(-1), y: cropRect.origin.y * CGFloat(-1))
    image.draw(at: origin)
    let result = UIGraphicsGetImageFromCurrentImageContext()
    UIGraphicsEndImageContext();
       
    return result
}

Image Rotation:

In image rotation we will use transformation matrix to rotate an image. In this example we will do three type of rotations: rotating an image by some degree(θ), flip the image vertically and flip the image horizontally. For these rotations we will use 3D rotation matrix.

To rotate an image by some degree(θ) we will rotate the image by θ degree w.r.t. Z-Axis. For vertical flip we will rotate the the image by 180 degree w.r.t. Y-Axis and for horizontal flip we will rotate the image by 180 degree w.r.t. X-Axis.

The 3D rotation matrix for rotation is following for different axises:

3d-rotation-matrix

 

 

 

 

 

 

Now we know that what will be our procedure to rotate and flip an image, So let’s start by defining a method for rotation.       

func rotateImage(image:UIImage, angle:CGFloat, flipVertical:CGFloat, flipHorizontal:CGFloat) -> UIImage? {
}

Here, `image` will be source image to be rotated, `angle` will be the rotation angle in radian, `flipVertical` will indicate whether the image will be flipped vertically or not by setting its value to 1 or 0 and `flipHorizontal` will indicate whether the image will be flipped horizontally or not by setting its value to 1 or 0. This method will return the UIImage object of rotated image.

To convert from degree to radian use following formula:

angleInRadian = angleInDegree * M_PI / 180

Now we will create a CIImage object of given source image and create a Affine Transform filter using CIFilter and set the CIImage object as input to the filter.  

let ciImage = CIImage(image: image)

let filter = CIFilter(name: "CIAffineTransform")
filter?.setValue(ciImage, forKey: kCIInputImageKey)
filter?.setDefaults()

By default Affine Transform rotate the image in anti-clockwise direction, so for clockwise rotation we have to multiply the angle by `-1`.

Now we will create a 3D identity transformation matrix.  

let newAngle = angle * CGFloat(-1)

var transform = CATransform3DIdentity

Apply rotation about Z-Axis by given angle:

 transform = CATransform3DRotate(transform, CGFloat(newAngle), 0, 0, 1)

Apply rotation about Y-Axis by radian (180 degrees) if flipVertical is 1.

transform = CATransform3DRotate(transform, CGFloat(Double(flipVertical) * M_PI), 0, 1, 0)

Apply rotation about X-Axis by radian (180 degrees) if flipHorizontal is 1.

transform = CATransform3DRotate(transform, CGFloat(Double(flipHorizontal) * M_PI), 1, 0, 0)

Now create an Affine transform object from above Transformation matrix and set it to the filter created earlier.    

let affineTransform = CATransform3DGetAffineTransform(transform)

filter?.setValue(NSValue(cgAffineTransform: affineTransform), forKey: "inputTransform")

Now get the rotated image and create a CIContext object for creating new image with extent of rotated image and convert it into UIImage and return. 

let contex = CIContext(options: [kCIContextUseSoftwareRenderer:true])

let outputImage = filter?.outputImage
let cgImage = contex.createCGImage(outputImage!, from: (outputImage?.extent)!)

let result = UIImage(cgImage: cgImage!)
return result

So the complete method for image rotation and flip will be following:

func rotateImage(image:UIImage, angle:CGFloat, flipVertical:CGFloat, flipHorizontal:CGFloat) -> UIImage? {
   let ciImage = CIImage(image: image)
       
   let filter = CIFilter(name: "CIAffineTransform")
   filter?.setValue(ciImage, forKey: kCIInputImageKey)
   filter?.setDefaults()
       
   let newAngle = angle * CGFloat(-1)
       
   var transform = CATransform3DIdentity
   transform = CATransform3DRotate(transform, CGFloat(newAngle), 0, 0, 1)
   transform = CATransform3DRotate(transform, CGFloat(Double(flipVertical) * M_PI), 0, 1, 0)
   transform = CATransform3DRotate(transform, CGFloat(Double(flipHorizontal) * M_PI), 1, 0, 0)
       
   let affineTransform = CATransform3DGetAffineTransform(transform)
       
   filter?.setValue(NSValue(cgAffineTransform: affineTransform), forKey: "inputTransform")
       
   let contex = CIContext(options: [kCIContextUseSoftwareRenderer:true])
       
   let outputImage = filter?.outputImage
   let cgImage = contex.createCGImage(outputImage!, from: (outputImage?.extent)!)
       
   let result = UIImage(cgImage: cgImage!)
   return result
}

Image Resizing:

Resizing is very frequently used operation in mobile applications while uploading an image to application server. Due to high resolution camera available on mobile devices we resize the image before uploading them to our application server.

So let’s define a method for image resizing:           

func resizeImage(image:UIImage, targetSize:CGSize) -> UIImage? {
}

Here, `image` is the source image to be resized and `targetSize` is a new size in which source image has to be resized. It returns an object of UIImage.

Calculate the width and height ratio of new image and derive a new size based on the minimum ratio and also make an rectangle using this new size.

 let originalSize = image.size
       
let widthRatio = targetSize.width / originalSize.width
let heightRatio = targetSize.height / originalSize.height
             
let ratio = min(widthRatio, heightRatio)
       
let newSize = CGSize(width: originalSize.width * ratio, height: originalSize.height * ratio)
       
// preparing rect for new image size
let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)

Now we will create an image context for drawing environment
UIGraphicsBeginImageContextWithOptions(newSize, false, UIScreen.main.scale)
Here we passed the size of resulting image as first parameter, second parameter indicates that the image is not opaque and third parameter specifies the image scale.

Now we will draw the source image into the new rectangle

image.draw(in: rect)

Now we will get the resulted image, close the image context and return the resulting UIImage object.

let result = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext();
       
return result

So the final method for Image resizing will be:

func resizeImage(image:UIImage, targetSize:CGSize) -> UIImage? {
     let originalSize = image.size
       
     let widthRatio = targetSize.width / originalSize.width
     let heightRatio = targetSize.height / originalSize.height

     let ratio = min(widthRatio, heightRatio)
       
     let newSize = CGSize(width: originalSize.width * ratio, height: originalSize.height * ratio)
       
     // preparing rect for new image size
     let rect = CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height)
       
     // Actually do the resizing to the rect using the ImageContext stuff
     UIGraphicsBeginImageContextWithOptions(newSize, false, UIScreen.main.scale)
     image.draw(in: rect)
       
     let newImage = UIGraphicsGetImageFromCurrentImageContext()
     UIGraphicsEndImageContext()
       
     return newImage
}

Leave a Reply