Skip to content

Instantly share code, notes, and snippets.

@erica
Created May 31, 2016 15:31
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save erica/157e20ea0c7e9f28a03a8b12448c8fd0 to your computer and use it in GitHub Desktop.
Save erica/157e20ea0c7e9f28a03a8b12448c8fd0 to your computer and use it in GitHub Desktop.
import UIKit
// Swift rewrite challenge
// Starting point: https://gist.github.com/jkereako/200342b66b5416fd715a#file-scale-and-crop-image-swift
func scaleAndCropImage(
image: UIImage,
toSize size: CGSize,
fitImage: Bool = true
) -> UIImage {
// Return original when cropping is not needed
guard !CGSizeEqualToSize(image.size, size) else { return image }
// Calculate scale factor for fit or fill
let (widthFactor, heightFactor) = (size.width / image.size.width, size.height / image.size.height)
let fitFillTest = fitImage ? widthFactor < heightFactor : widthFactor > heightFactor
let scaleFactor = fitFillTest ? widthFactor : heightFactor
// Establish drawing destination, which may start outside the drawing context bounds
let (scaledWidth, scaledHeight) = (image.size.width * scaleFactor, image.size.height * scaleFactor)
let drawingOrigin = CGPoint(
x: (size.width - scaledWidth) / 2.0,
y: (size.height - scaledHeight) / 2.0)
// Perform drawing and return image
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
let scaledImage: UIImage
do {
// Fill background
UIColor.blackColor().setFill(); UIRectFill(CGRect(origin: .zero, size: size))
// Draw scaled image
let drawingRect: CGRect = CGRect(
origin: drawingOrigin,
size: CGSize(width: scaledWidth, height: scaledHeight))
image.drawInRect(drawingRect)
// Fetch image
scaledImage = UIGraphicsGetImageFromCurrentImageContext()!
}
UIGraphicsEndImageContext()
return scaledImage
}
// Test with some basic placeholder data
guard let url = NSURL(string: "http://placehold.it/300x150") else { fatalError("Bad URL") }
guard let data = NSData(contentsOfURL: url) else { fatalError("Bad data") }
guard let img = UIImage(data: data) else { fatalError("Bad data") }
let outImageFit = scaleAndCropImage(img, toSize: CGSize(width: 200, height: 200))
let outImageFill = scaleAndCropImage(img, toSize: CGSize(width: 200, height: 200), fitImage: false)
@oleksii-demedetskyi
Copy link

I would recommend to split into several functions (size calculations, image cropping, glue) for unit testing support.

my version

@erica
Copy link
Author

erica commented May 31, 2016

Add asserts for size.width and size.height > 0.0 or some minimum point size as desired. See: http://twitter.com/deadbeefa/status/737722278608142345

@bshirley
Copy link

bshirley commented May 31, 2016

Enjoyed the light challenge.
Here's my full rewrite (FWIW), it doesn't add the functionality you added.
I like your use of do and tuples.
My preference is for an extension of UIImage.
I still maintained an if/else/else.

also re: "http://placehold.it/300x150" … i heard angels sing (new to me and quite useful!)

extension UIImage {
  func scaleAndCrop(size: CGSize) -> UIImage {
    guard CGSizeEqualToSize(self.size, size) == false else {
      return self
    }

    let widthFactor = size.width / self.size.width
    let heightFactor = size.height / self.size.height
    let scaleFactor = max(widthFactor, heightFactor)
    let scaledWidth = self.size.width * scaleFactor
    let scaledHeight = self.size.height * scaleFactor

    var scaledRect: CGRect
    if widthFactor > heightFactor {
      scaledRect = CGRect(x: 0.0, y: (size.height - scaledHeight) / 2.0,
                          width: scaledWidth, height: scaledHeight)
    } else if widthFactor < heightFactor {
      scaledRect = CGRect(x: (size.width - scaledWidth) / 2.0, y: 0,
                          width: scaledWidth, height: scaledHeight)
    } else {
      scaledRect = CGRect(x: 0, y: 0,
                          width: scaledWidth, height: scaledHeight)
    }

    UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
    self.drawInRect(scaledRect)
    let scaledImage = UIGraphicsGetImageFromCurrentImageContext()!
    UIGraphicsEndImageContext()

    return scaledImage
  }
}

@PaulTaykalo
Copy link

Useful method for performing drawing in the resulting context
Using calculation of x, y in both branches instead of checking for if widthFactor > heightFactor

import UIKit

extension UIImage {

    /**
     Creates image of specified size and perfomrs drawing commands

     - parameter size:    result image size
     - parameter drawing: closure that contains deawing operations

     - returns: image with the drawing operation
     */
    func drawingWithSize(size:CGSize, @noescape drawing:()->()) -> UIImage {
        UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
        drawing()
        let image = UIGraphicsGetImageFromCurrentImageContext()!
        UIGraphicsEndImageContext()
        return image
    }

    /**
     Scales image to specified size, savin aspect ratio
     Crops parts of image that out of provided size 
     Works as UICntentModeAspectFill

     - parameter size: result image size

     - returns: scaled image
     */
    func scaleAndCrop(toSize size:CGSize) -> UIImage {

        // Skip unneeded scaling
        guard CGSizeEqualToSize(self.size, size) == false else {
            return self
        }

        let scaleFactor = max(size.width / self.size.width,
                              size.height / self.size.height)

        let (scaledWidth, scaledHeight) = (self.size.width * scaleFactor,
                                           self.size.height * scaleFactor)

        let drawingRect = CGRectMake(
            (size.width - scaledWidth) / 2.0,
            (size.height - scaledHeight) / 2.0,
            scaledWidth,
            scaledHeight)

        let scaledImage = drawingWithSize(size) {
            self.drawInRect(drawingRect)
        }
        return scaledImage
    }
}

@erica
Copy link
Author

erica commented May 31, 2016

@PaulTaykalo

In my real world code, I use code that passes a context. You can always get context from a valid drawing session and UIKit supports a context stack, so you can push the context, perform drawing, and then pull an image to return and pop the context.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment