Skip to content

Instantly share code, notes, and snippets.

@bgreenlee
Created November 23, 2015 05:24
Show Gist options
  • Save bgreenlee/c81b169cfb8337ce5140 to your computer and use it in GitHub Desktop.
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/
//
// 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
}
}
@Jaymassena
Copy link

In case someone finds it useful, I updated the roundedCornerImage and addRoundedRectToPath functions to support swift 4.0:

    // 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/
    public func roundedCornerImage(cornerRadius: Int, borderSize: Int) -> UIImage {
        // If the image does not have an alpha layer, add one
        let image = self.withAlpha()!
        
        // Build a context that's the same dimensions as the new size
        let colorSpace: CGColorSpace = cgImage!.colorSpace!
        let bitmapInfo = CGBitmapInfo(arrayLiteral: cgImage!.bitmapInfo)
        let context = CGContext.init(
            data: nil,
            width: Int(image.size.width),
            height: Int(image.size.height),
            bitsPerComponent: cgImage!.bitsPerComponent,
            bytesPerRow: 0,
            space: colorSpace,
            bitmapInfo: bitmapInfo.rawValue
        )!
        
        // Create a clipping path with rounded corners
        context.beginPath()
        self.addRoundedRectToPath(
            rect: CGRect(x: CGFloat(borderSize)
                , y: CGFloat(borderSize)
                , width: image.size.width - CGFloat(borderSize) * 2
                , height: image.size.height - CGFloat(borderSize) * 2),
            context: context,
            ovalWidth: CGFloat(cornerRadius),
            ovalHeight: CGFloat(cornerRadius)
        )
        context.closePath()
        context.clip()
        
        // Draw the image to the context; the clipping path will make anything outside the rounded rect transparent
        context.draw(cgImage!, in: CGRect(x:0, y:0, width: image.size.width, height: image.size.height))
        
        // Create a CGImage from the context
        let clippedImage = context.makeImage()!
        
        // Create a UIImage from the CGImage
        return UIImage(cgImage: clippedImage)
    }
    
    // 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: CGContext, ovalWidth: CGFloat, ovalHeight: CGFloat) {
        if (ovalWidth == 0 || ovalHeight == 0) {
            context.addRect(rect)
            return
        }
        
        context.saveGState()
        context.translateBy(x: rect.minX, y: rect.minY)
        context.scaleBy(x: ovalWidth, y: ovalHeight)
        let fw = rect.width / ovalWidth
        let fh = rect.height / ovalHeight
        context.move(to: CGPoint(x: fw, y: fh/2))
        context.addArc(tangent1End: CGPoint(x: fw, y: fh), tangent2End: CGPoint(x: fw/2, y: fh), radius: 1)
        context.addArc(tangent1End: CGPoint(x: 0, y: fh), tangent2End: CGPoint(x: 0, y: fh/2), radius: 1)
        context.addArc(tangent1End: CGPoint(x: 0, y: 0), tangent2End: CGPoint(x: fw/2, y: 0), radius: 1)
        context.addArc(tangent1End: CGPoint(x: fw, y: 0), tangent2End: CGPoint(x: fw, y: fh/2), radius: 1)
        context.closePath();
        context.restoreGState()
    }

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