Created
January 4, 2023 14:08
-
-
Save AndyQ/fe838d8c998c8d8467e6fe6da4af850e to your computer and use it in GitHub Desktop.
UIImage extension in Swift to resize images
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 ImageIO | |
import Accelerate | |
extension UIImage { | |
public enum ResizeFramework { | |
case uikit, coreImage, coreGraphics, imageIO, accelerate | |
} | |
/// Resize image with ScaleAspectFit mode and given size. | |
/// | |
/// - Parameter dimension: width or length of the image output. | |
/// - Parameter resizeFramework: Technique for image resizing: UIKit / CoreImage / CoreGraphics / ImageIO / Accelerate. | |
/// - Returns: Resized image. | |
func resizeWithScaleAspectFitMode(to dimension: CGFloat, resizeFramework: ResizeFramework = .coreGraphics) -> UIImage? { | |
if max(size.width, size.height) <= dimension { return self } | |
var newSize: CGSize! | |
let aspectRatio = size.width/size.height | |
if aspectRatio > 1 { | |
// Landscape image | |
newSize = CGSize(width: dimension, height: dimension / aspectRatio) | |
} else { | |
// Portrait image | |
newSize = CGSize(width: dimension * aspectRatio, height: dimension) | |
} | |
return resize(to: newSize, with: resizeFramework) | |
} | |
/// Resize image from given size. | |
/// | |
/// - Parameter newSize: Size of the image output. | |
/// - Parameter resizeFramework: Technique for image resizing: UIKit / CoreImage / CoreGraphics / ImageIO / Accelerate. | |
/// - Returns: Resized image. | |
public func resize(to newSize: CGSize, with resizeFramework: ResizeFramework = .coreGraphics) -> UIImage? { | |
switch resizeFramework { | |
case .uikit: return resizeWithUIKit(to: newSize) | |
case .coreGraphics: return resizeWithCoreGraphics(to: newSize) | |
case .coreImage: return resizeWithCoreImage(to: newSize) | |
case .imageIO: return resizeWithImageIO(to: newSize) | |
case .accelerate: return resizeWithAccelerate(to: newSize) | |
} | |
} | |
// MARK: - UIKit | |
/// Resize image from given size. | |
/// | |
/// - Parameter newSize: Size of the image output. | |
/// - Returns: Resized image. | |
private func resizeWithUIKit(to newSize: CGSize) -> UIImage? { | |
UIGraphicsBeginImageContextWithOptions(newSize, true, 1.0) | |
self.draw(in: CGRect(origin: .zero, size: newSize)) | |
defer { UIGraphicsEndImageContext() } | |
return UIGraphicsGetImageFromCurrentImageContext() | |
} | |
// MARK: - CoreImage | |
/// Resize CI image from given size. | |
/// | |
/// - Parameter newSize: Size of the image output. | |
/// - Returns: Resized image. | |
// https://developer.apple.com/library/archive/documentation/GraphicsImaging/Reference/CoreImageFilterReference/index.html | |
private func resizeWithCoreImage(to newSize: CGSize) -> UIImage? { | |
guard let cgImage = cgImage, let filter = CIFilter(name: "CILanczosScaleTransform") else { return nil } | |
let ciImage = CIImage(cgImage: cgImage) | |
let scale = (Double)(newSize.width) / (Double)(ciImage.extent.size.width) | |
filter.setValue(ciImage, forKey: kCIInputImageKey) | |
filter.setValue(NSNumber(value:scale), forKey: kCIInputScaleKey) | |
filter.setValue(1.0, forKey: kCIInputAspectRatioKey) | |
guard let outputImage = filter.value(forKey: kCIOutputImageKey) as? CIImage else { return nil } | |
let context = CIContext(options: [.useSoftwareRenderer: false]) | |
guard let resultCGImage = context.createCGImage(outputImage, from: outputImage.extent) else { return nil } | |
return UIImage(cgImage: resultCGImage) | |
} | |
// MARK: - CoreGraphics | |
/// Resize image from given size. | |
/// | |
/// - Parameter newSize: Size of the image output. | |
/// - Returns: Resized image. | |
private func resizeWithCoreGraphics(to newSize: CGSize) -> UIImage? { | |
guard let cgImage = cgImage, let colorSpace = cgImage.colorSpace else { return nil } | |
let width = Int(newSize.width) | |
let height = Int(newSize.height) | |
let bitsPerComponent = cgImage.bitsPerComponent | |
let bytesPerRow = cgImage.bytesPerRow | |
let bitmapInfo = cgImage.bitmapInfo | |
guard let context = CGContext(data: nil, width: width, height: height, | |
bitsPerComponent: bitsPerComponent, | |
bytesPerRow: bytesPerRow, space: colorSpace, | |
bitmapInfo: bitmapInfo.rawValue) else { return nil } | |
context.interpolationQuality = .high | |
let rect = CGRect(origin: CGPoint.zero, size: newSize) | |
context.draw(cgImage, in: rect) | |
return context.makeImage().flatMap { UIImage(cgImage: $0) } | |
} | |
// MARK: - ImageIO | |
/// Resize image from given size. | |
/// | |
/// - Parameter newSize: Size of the image output. | |
/// - Returns: Resized image. | |
private func resizeWithImageIO(to newSize: CGSize) -> UIImage? { | |
var resultImage = self | |
guard let data = jpegData(compressionQuality: 1.0) else { return resultImage } | |
let imageCFData = NSData(data: data) as CFData | |
let options = [ | |
kCGImageSourceCreateThumbnailWithTransform: true, | |
kCGImageSourceCreateThumbnailFromImageAlways: true, | |
kCGImageSourceThumbnailMaxPixelSize: max(newSize.width, newSize.height) | |
] as CFDictionary | |
guard let source = CGImageSourceCreateWithData(imageCFData, nil), | |
let imageReference = CGImageSourceCreateThumbnailAtIndex(source, 0, options) else { return resultImage } | |
resultImage = UIImage(cgImage: imageReference) | |
return resultImage | |
} | |
// MARK: - Accelerate | |
/// Resize image from given size. | |
/// | |
/// - Parameter newSize: Size of the image output. | |
/// - Returns: Resized image. | |
private func resizeWithAccelerate(to newSize: CGSize) -> UIImage? { | |
var resultImage = self | |
guard let cgImage = cgImage, let colorSpace = cgImage.colorSpace else { return nil } | |
// create a source buffer | |
var format = vImage_CGImageFormat(bitsPerComponent: numericCast(cgImage.bitsPerComponent), | |
bitsPerPixel: numericCast(cgImage.bitsPerPixel), | |
colorSpace: Unmanaged.passUnretained(colorSpace), | |
bitmapInfo: cgImage.bitmapInfo, | |
version: 0, | |
decode: nil, | |
renderingIntent: .absoluteColorimetric) | |
var sourceBuffer = vImage_Buffer() | |
defer { | |
sourceBuffer.data.deallocate() | |
} | |
var error = vImageBuffer_InitWithCGImage(&sourceBuffer, &format, nil, cgImage, numericCast(kvImageNoFlags)) | |
guard error == kvImageNoError else { return resultImage } | |
// create a destination buffer | |
let destWidth = Int(newSize.width) | |
let destHeight = Int(newSize.height) | |
let bytesPerPixel = cgImage.bitsPerPixel | |
let destBytesPerRow = destWidth * bytesPerPixel | |
let destData = UnsafeMutablePointer<UInt8>.allocate(capacity: destHeight * destBytesPerRow) | |
defer { | |
destData.deallocate() | |
} | |
var destBuffer = vImage_Buffer(data: destData, height: vImagePixelCount(destHeight), width: vImagePixelCount(destWidth), rowBytes: destBytesPerRow) | |
// scale the image | |
error = vImageScale_ARGB8888(&sourceBuffer, &destBuffer, nil, numericCast(kvImageHighQualityResampling)) | |
guard error == kvImageNoError else { return resultImage } | |
// create a CGImage from vImage_Buffer | |
let destCGImage = vImageCreateCGImageFromBuffer(&destBuffer, &format, nil, nil, numericCast(kvImageNoFlags), &error)?.takeRetainedValue() | |
guard error == kvImageNoError else { return resultImage } | |
// create a UIImage | |
if let scaledImage = destCGImage.flatMap({ UIImage(cgImage: $0) }) { | |
resultImage = scaledImage | |
} | |
return resultImage | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment