-
-
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) |
I would recommend to split into several functions (size calculations, image cropping, glue) for unit testing support.
Add asserts for size.width and size.height > 0.0 or some minimum point size as desired. See: http://twitter.com/deadbeefa/status/737722278608142345
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
}
}
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
}
}
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.
I did not have a guard to prevent needless scaling in my version of this code. Good Stuff!
But I think you will like this
defer
: (had not refreshed the page to see your new comments...)And maybe this bit :
my version