Created
May 31, 2016 15:31
-
-
Save erica/157e20ea0c7e9f28a03a8b12448c8fd0 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
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.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add asserts for size.width and size.height > 0.0 or some minimum point size as desired. See: http://twitter.com/deadbeefa/status/737722278608142345