Created
November 23, 2015 05:24
-
-
Save bgreenlee/c81b169cfb8337ce5140 to your computer and use it in GitHub Desktop.
Swift port of UIImage+Resize, UIImage+Alpha, and UIImage+RoundedCorner, from http://vocaro.com/trevor/blog/2009/10/12/resize-a-uiimage-the-right-way/
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
// | |
// UIImage+Resize.swift | |
// Port of UIImage+Resize.m | |
// from http://vocaro.com/trevor/blog/2009/10/12/resize-a-uiimage-the-right-way/ | |
// | |
import Foundation | |
import UIKit | |
extension UIImage { | |
// | |
// UIImage+Alpha | |
// http://vocaro.com/trevor/blog/wp-content/uploads/2009/10/UIImage+Alpha.m | |
// | |
// Returns true if the image has an alpha layer | |
func hasAlpha() -> Bool { | |
let alpha = CGImageGetAlphaInfo(self.CGImage) | |
return alpha == .First || alpha == .Last || alpha == .PremultipliedFirst || alpha == .PremultipliedLast | |
} | |
// Returns a copy of the given image, adding an alpha channel if it doesn't already have one | |
func imageWithAlpha() -> UIImage? { | |
if hasAlpha() { | |
return self | |
} | |
let imageRef = self.CGImage | |
let width = CGImageGetWidth(imageRef) | |
let height = CGImageGetHeight(imageRef) | |
// The bitsPerComponent and bitmapInfo values are hard-coded to prevent an "unsupported parameter combination" error | |
let offscreenContext = CGBitmapContextCreate(nil, width, height, 8, 0, CGImageGetColorSpace(imageRef), CGBitmapInfo.ByteOrderDefault.rawValue | CGImageAlphaInfo.PremultipliedFirst.rawValue) | |
// Draw the image into the context and retrieve the new image, which will now have an alpha layer | |
CGContextDrawImage(offscreenContext, CGRectMake(0, 0, CGFloat(width), CGFloat(height)), imageRef) | |
if let imageRefWithAlpha = CGBitmapContextCreateImage(offscreenContext) { | |
return UIImage(CGImage: imageRefWithAlpha) | |
} else { | |
return nil | |
} | |
} | |
// Creates a mask that makes the outer edges transparent and everything else opaque | |
// The size must include the entire mask (opaque part + transparent border) | |
// The caller is responsible for releasing the returned reference by calling CGImageRelease | |
private func newBorderMask(borderSize: UInt, size: CGSize) -> CGImageRef? { | |
let colorSpace = CGColorSpaceCreateDeviceGray() | |
// Build a context that's the same dimensions as the new size | |
let maskContext = CGBitmapContextCreate(nil, Int(size.width), Int(size.height), | |
8, // 8-bit grayscale | |
0, | |
colorSpace, | |
CGBitmapInfo.ByteOrderDefault.rawValue | CGImageAlphaInfo.PremultipliedFirst.rawValue); | |
// Start with a mask that's entirely transparent | |
CGContextSetFillColorWithColor(maskContext, UIColor.blackColor().CGColor) | |
CGContextFillRect(maskContext, CGRectMake(0, 0, size.width, size.height)) | |
// Make the inner part (within the border) opaque | |
CGContextSetFillColorWithColor(maskContext, UIColor.whiteColor().CGColor); | |
CGContextFillRect(maskContext, CGRectMake(CGFloat(borderSize), CGFloat(borderSize), size.width - CGFloat(borderSize) * 2.0, size.height - CGFloat(borderSize) * 2.0)) | |
// Get an image of the context | |
return CGBitmapContextCreateImage(maskContext) | |
} | |
// Returns a copy of the image with a transparent border of the given size added around its edges. | |
// If the image has no alpha layer, one will be added to it. | |
func transparentBorderImage(borderSize: UInt) -> UIImage? { | |
// If the image does not have an alpha layer, add one | |
if let image = imageWithAlpha() { | |
let newRect = CGRectMake(0, 0, image.size.width + CGFloat(borderSize) * 2.0, image.size.height + CGFloat(borderSize) * 2.0) | |
// Build a context that's the same dimensions as the new size | |
let bitmap = CGBitmapContextCreate(nil, Int(newRect.size.width), Int(newRect.size.height), CGImageGetBitsPerComponent(self.CGImage), 0, | |
CGImageGetColorSpace(self.CGImage), CGImageGetBitmapInfo(self.CGImage).rawValue) | |
// Draw the image in the center of the context, leaving a gap around the edges | |
let imageLocation = CGRectMake(CGFloat(borderSize), CGFloat(borderSize), image.size.width, image.size.height) | |
CGContextDrawImage(bitmap, imageLocation, self.CGImage) | |
let borderImageRef = CGBitmapContextCreateImage(bitmap) | |
// Create a mask to make the border transparent, and combine it with the image | |
let maskImageRef = newBorderMask(borderSize, size: newRect.size) | |
if let transparentBorderImageRef = CGImageCreateWithMask(borderImageRef, maskImageRef) { | |
return UIImage(CGImage: transparentBorderImageRef) | |
} else { | |
return nil | |
} | |
} | |
return nil | |
} | |
// Adds a rectangular path to the given context and rounds its corners by the given extents | |
// Original author: Björn Sållarp. Used with permission. See: http://blog.sallarp.com/iphone-uiimage-round-corners/ | |
private func addRoundedRectToPath(rect: CGRect, context: CGContextRef, ovalWidth: CGFloat, ovalHeight: CGFloat) { | |
if (ovalWidth == 0 || ovalHeight == 0) { | |
CGContextAddRect(context, rect) | |
return | |
} | |
CGContextSaveGState(context) | |
CGContextTranslateCTM(context, CGRectGetMinX(rect), CGRectGetMinY(rect)) | |
CGContextScaleCTM(context, ovalWidth, ovalHeight) | |
let fw = CGRectGetWidth(rect) / ovalWidth | |
let fh = CGRectGetHeight(rect) / ovalHeight | |
CGContextMoveToPoint(context, fw, fh/2) | |
CGContextAddArcToPoint(context, fw, fh, fw/2, fh, 1) | |
CGContextAddArcToPoint(context, 0, fh, 0, fh/2, 1) | |
CGContextAddArcToPoint(context, 0, 0, fw/2, 0, 1) | |
CGContextAddArcToPoint(context, fw, 0, fw, fh/2, 1) | |
CGContextClosePath(context) | |
CGContextRestoreGState(context) | |
} | |
// | |
// UIImage+RoundedCorner | |
// http://vocaro.com/trevor/blog/wp-content/uploads/2009/10/UIImage+RoundedCorner.m | |
// | |
// Creates a copy of this image with rounded corners | |
// If borderSize is non-zero, a transparent border of the given size will also be added | |
// Original author: Björn Sållarp. Used with permission. See: http://blog.sallarp.com/iphone-uiimage-round-corners/ | |
func roundedCornerImage(cornerSize:Int, borderSize: Int) -> UIImage? { | |
// If the image does not have an alpha layer, add one | |
if let image = imageWithAlpha() { | |
// Build a context that's the same dimensions as the new size | |
if let context = CGBitmapContextCreate(nil, Int(image.size.width), Int(image.size.height), | |
CGImageGetBitsPerComponent(image.CGImage), 0, CGImageGetColorSpace(image.CGImage), CGImageGetBitmapInfo(image.CGImage).rawValue) { | |
// Create a clipping path with rounded corners | |
CGContextBeginPath(context) | |
addRoundedRectToPath(CGRectMake(CGFloat(borderSize), CGFloat(borderSize), image.size.width - CGFloat(borderSize) * 2.0, | |
image.size.height - CGFloat(borderSize) * 2.0), context:context, ovalWidth:CGFloat(cornerSize), ovalHeight:CGFloat(cornerSize)) | |
CGContextClosePath(context) | |
CGContextClip(context) | |
// Draw the image to the context; the clipping path will make anything outside the rounded rect transparent | |
CGContextDrawImage(context, CGRectMake(0, 0, image.size.width, image.size.height), image.CGImage) | |
// Create a CGImage from the context | |
if let clippedImage = CGBitmapContextCreateImage(context) { | |
return UIImage(CGImage:clippedImage) | |
} | |
} | |
} | |
return nil | |
} | |
// | |
// UIImage+Resize | |
// http://vocaro.com/trevor/blog/wp-content/uploads/2009/10/UIImage+Resize.m | |
// | |
// Returns a rescaled copy of the image, taking into account its orientation | |
// The image will be scaled disproportionately if necessary to fit the bounds specified by the parameter | |
func resizedImage(newSize: CGSize, quality: CGInterpolationQuality) -> UIImage? { | |
var drawTransposed: Bool | |
switch (imageOrientation) { | |
case .Left: fallthrough | |
case .LeftMirrored: fallthrough | |
case .Right: fallthrough | |
case .RightMirrored: | |
drawTransposed = true | |
default: | |
drawTransposed = false | |
} | |
return resizedImage(newSize, transform: transformForOrientation(newSize), transpose: drawTransposed, quality: quality) | |
} | |
// Returns a copy of the image that has been transformed using the given affine transform and scaled to the new size | |
// The new image's orientation will be UIImageOrientationUp, regardless of the current image's orientation | |
// If the new size is not integral, it will be rounded up | |
private func resizedImage(newSize: CGSize, transform: CGAffineTransform, transpose: Bool, quality: CGInterpolationQuality) -> UIImage? { | |
let newRect = CGRectIntegral(CGRectMake(0, 0, newSize.width, newSize.height)) | |
let transposedRect = CGRectMake(0, 0, newRect.size.height, newRect.size.width) | |
let imageRef = self.CGImage; | |
// Build a context that's the same dimensions as the new size | |
let bitmap = CGBitmapContextCreate(nil, Int(newRect.size.width), Int(newRect.size.height), CGImageGetBitsPerComponent(imageRef), | |
0, CGImageGetColorSpace(imageRef), CGImageGetBitmapInfo(imageRef).rawValue) | |
// Rotate and/or flip the image if required by its orientation | |
CGContextConcatCTM(bitmap, transform) | |
// Set the quality level to use when rescaling | |
CGContextSetInterpolationQuality(bitmap, quality) | |
// Draw into the context; this scales the image | |
CGContextDrawImage(bitmap, transpose ? transposedRect : newRect, imageRef); | |
// Get the resized image from the context and a UIImage | |
if let newImageRef = CGBitmapContextCreateImage(bitmap) { | |
return UIImage(CGImage: newImageRef) | |
} | |
return nil | |
} | |
// Returns a copy of this image that is cropped to the given bounds. | |
// The bounds will be adjusted using CGRectIntegral. | |
// This method ignores the image's imageOrientation setting. | |
func croppedImage(bounds: CGRect) -> UIImage? { | |
if let imageRef = CGImageCreateWithImageInRect(self.CGImage, bounds) { | |
return UIImage(CGImage: imageRef) | |
} | |
return nil | |
} | |
// Returns a copy of this image that is squared to the thumbnail size. | |
// If transparentBorder is non-zero, a transparent border of the given size will be added around the edges of the thumbnail. (Adding a transparent border of at least one pixel in size has the side-effect of antialiasing the edges of the image when rotating it using Core Animation.) | |
func thumbnailImage(thumbnailSize: Int, borderSize: Int, cornerRadius: Int, quality: CGInterpolationQuality) -> UIImage? { | |
if let resizedImage = resizedImageWithContentMode(.ScaleAspectFill, bounds: CGSizeMake(CGFloat(thumbnailSize), CGFloat(thumbnailSize)), quality: quality) { | |
// Crop out any part of the image that's larger than the thumbnail size | |
// The cropped rect must be centered on the resized image | |
// Round the origin points so that the size isn't altered when CGRectIntegral is later invoked | |
let cropRect = CGRectMake(round((resizedImage.size.width - CGFloat(thumbnailSize)) / 2), | |
round((resizedImage.size.height - CGFloat(thumbnailSize)) / 2), | |
CGFloat(thumbnailSize), CGFloat(thumbnailSize)) | |
if let croppedImage = resizedImage.croppedImage(cropRect) { | |
if let transparentBorderImage = borderSize > 0 ? croppedImage.transparentBorderImage(UInt(borderSize)) : croppedImage { | |
return transparentBorderImage.roundedCornerImage(cornerRadius, borderSize: borderSize) | |
} | |
} | |
} | |
return nil | |
} | |
// Resizes the image according to the given content mode, taking into account the image's orientation | |
func resizedImageWithContentMode(contentMode: UIViewContentMode, bounds: CGSize, quality: CGInterpolationQuality) -> UIImage? { | |
let horizontalRatio = bounds.width / self.size.width | |
let verticalRatio = bounds.height / self.size.height | |
var ratio: CGFloat! | |
switch (contentMode) { | |
case .ScaleAspectFill: | |
ratio = max(horizontalRatio, verticalRatio) | |
case .ScaleAspectFit: | |
ratio = min(horizontalRatio, verticalRatio) | |
default: | |
NSException.raise(NSInvalidArgumentException, format: "Unsupported content mode: %d", arguments:getVaList([contentMode.rawValue])) | |
} | |
let newSize = CGSizeMake(self.size.width * ratio, self.size.height * ratio) | |
return resizedImage(newSize, quality: quality) | |
} | |
// Returns an affine transform that takes into account the image orientation when drawing a scaled image | |
func transformForOrientation(newSize: CGSize) -> CGAffineTransform { | |
var transform = CGAffineTransformIdentity | |
switch (self.imageOrientation) { | |
case .Down: fallthrough // EXIF = 3 | |
case .DownMirrored: // EXIF = 4 | |
transform = CGAffineTransformTranslate(transform, newSize.width, newSize.height) | |
transform = CGAffineTransformRotate(transform, CGFloat(M_PI)) | |
case .Left: fallthrough // EXIF = 6 | |
case .LeftMirrored: // EXIF = 5 | |
transform = CGAffineTransformTranslate(transform, newSize.width, 0) | |
transform = CGAffineTransformRotate(transform, CGFloat(M_PI_2)) | |
case .Right: fallthrough // EXIF = 8 | |
case .RightMirrored: // EXIF = 7 | |
transform = CGAffineTransformTranslate(transform, 0, newSize.height) | |
transform = CGAffineTransformRotate(transform, -CGFloat(M_PI_2)) | |
default: break | |
} | |
switch (self.imageOrientation) { | |
case .UpMirrored: fallthrough // EXIF = 2 | |
case .DownMirrored: // EXIF = 4 | |
transform = CGAffineTransformTranslate(transform, newSize.width, 0) | |
transform = CGAffineTransformScale(transform, -1, 1) | |
case .LeftMirrored: fallthrough // EXIF = 5 | |
case .RightMirrored: // EXIF = 7 | |
transform = CGAffineTransformTranslate(transform, newSize.height, 0) | |
transform = CGAffineTransformScale(transform, -1, 1) | |
default: break | |
} | |
return transform | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
In case someone finds it useful, I updated the roundedCornerImage and addRoundedRectToPath functions to support swift 4.0: