Last active
June 5, 2016 12:14
-
-
Save trs123/e65a4b6430af327796c1c8769f14ca00 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 by Erica Sadun: http://ericasadun.com/2016/05/31/swift-rewrite-challenge/ | |
// Starting point: https://gist.github.com/erica/157e20ea0c7e9f28a03a8b12448c8fd0 | |
// My solution builds on a small framework I have written with some geometry extensions on CGVector, CGPoint, CGRect and CGSize | |
// I call it GeometryKit and include the relevant parts here. This makes geometric calculations so much easier to understand. | |
// ------------------------------------------------------------------------------------ | |
// GeometryKit | |
// ------------------------------------------------------------------------------------ | |
extension CGVector { | |
/// Vector with zero length | |
public static func zero() -> CGVector { | |
return CGVector() | |
} | |
/// Vector pointing in same direction as receiver but scale times as long | |
public func scaledBy(scale: CGFloat) -> CGVector { | |
return CGVector(dx: dx * scale, dy: dy * scale) | |
} | |
/// Vector pointing in same direction as receiver but half as long | |
public var half: CGVector { | |
return scaledBy(0.5) | |
} | |
/// Vector of same length as receiver but pointing in opposite direction | |
public var negated: CGVector { | |
return CGVector(dx: -dx, dy: -dy) | |
} | |
} | |
/// Sum of two vectors | |
public func +(v1: CGVector, v2: CGVector) -> CGVector { | |
return CGVector(dx: v1.dx + v2.dx, dy: v1.dy + v2.dy) | |
} | |
/// Difference of two vectors | |
public func -(v1: CGVector, v2: CGVector) -> CGVector { | |
return CGVector(dx: v1.dx - v2.dx, dy: v1.dy - v2.dy) | |
} | |
public extension CGPoint { | |
/// CGVector from origin to receiver | |
public func asVector() -> CGVector { | |
return CGVector(dx: x, dy: y) | |
} | |
/// Point offset from receiver by vector | |
public func offsetBy(v: CGVector) -> CGPoint { | |
return CGPoint(x: x + v.dx, y: y + v.dy) | |
} | |
/// Vector from receiver to other point | |
public func vectorTo(other: CGPoint) -> CGVector { | |
return other.asVector() - asVector() | |
} | |
} | |
public extension CGRect { | |
/// Initialize Rect from center location and size | |
public init(center: CGPoint, size: CGSize) { | |
self.init(origin: center.offsetBy(size.asVector().half.negated), size: size) | |
} | |
/// Initialize Rect from size | |
public init(size: CGSize) { | |
self.init(origin: .zero, size: size) | |
} | |
/// Location of receiver's center | |
public var center: CGPoint { | |
return origin.offsetBy(size.asVector().half) | |
} | |
} | |
public extension CGSize { | |
/// Convert receiver to vector | |
public func asVector() -> CGVector { | |
return CGVector(dx: width, dy: height) | |
} | |
/// Scaled by factor | |
public func scaledBy(scale: CGFloat) -> CGSize { | |
return CGSize(width: width * scale, height: height * scale) | |
} | |
/// Size of receiver scaled to fit the given size | |
public func aspectFit(size: CGSize) -> CGSize { | |
let scaleX = size.width / width | |
let scaleY = size.height / height | |
let scale = min(scaleX, scaleY) | |
return scaledBy(scale) | |
} | |
/// Size of receiver scaled to fill the given size | |
public func aspectFill(size: CGSize) -> CGSize { | |
let scaleX = size.width / width | |
let scaleY = size.height / height | |
let scale = max(scaleX, scaleY) | |
return scaledBy(scale) | |
} | |
} | |
public extension UIImage { | |
public var bounds: CGRect { return CGRect(origin: .zero, size: size) } | |
} | |
/// Draw on graphics context | |
func drawImageWithOptions(size: CGSize, opaque: Bool, scale: CGFloat, draw: (withinBounds: CGRect) -> ()) -> UIImage | |
{ | |
UIGraphicsBeginImageContextWithOptions(size, opaque, scale) | |
let bounds = CGRect(size: size) | |
draw(withinBounds: bounds) | |
let image = UIGraphicsGetImageFromCurrentImageContext()! | |
UIGraphicsEndImageContext() | |
return image | |
} | |
// ------------------------------------------------------------------------------------ | |
// My Scaling Function | |
// ------------------------------------------------------------------------------------ | |
/// Scaling mode for scaling CGSize values | |
enum ScalingMode { | |
case fit | |
case fill | |
case unscaled | |
func scale(size: CGSize, within targetSize: CGSize) -> CGSize { | |
switch self { | |
case .fit: return size.aspectFit(targetSize) | |
case .fill: return size.aspectFill(targetSize) | |
case .unscaled: return size | |
} | |
} | |
} | |
/// Scale image to given size using given scaling mode (default: .fit) | |
func scaleImage( | |
image: UIImage, | |
toSize size: CGSize, | |
scalingMode: ScalingMode = .fit | |
) -> UIImage { | |
// Return original when cropping is not needed | |
guard image.size != size else { return image } | |
// Calculate scaled size for scaling mode | |
let scaledSize = scalingMode.scale(image.size, within: size) | |
// Perform drawing and return image | |
return drawImageWithOptions(size, opaque: false, scale: 0.0) { bounds in | |
// Fill background | |
UIColor.blackColor().setFill(); UIRectFill(bounds) | |
// Draw scaled image centered within bounds | |
image.drawInRect(CGRect(center: bounds.center, size: scaledSize)) | |
} | |
} | |
// 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 = scaleImage(img, toSize: CGSize(width: 200, height: 200)) | |
let outImageFill = scaleImage(img, toSize: CGSize(width: 200, height: 200), scalingMode: .fill) | |
let outImageCentered = scaleImage(img, toSize: CGSize(width: 200, height: 200), scalingMode: .unscaled) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment