Skip to content

Instantly share code, notes, and snippets.

@AndyQ
Created January 4, 2023 14:08
Show Gist options
  • Select an option

  • Save AndyQ/fe838d8c998c8d8467e6fe6da4af850e to your computer and use it in GitHub Desktop.

Select an option

Save AndyQ/fe838d8c998c8d8467e6fe6da4af850e to your computer and use it in GitHub Desktop.

Revisions

  1. AndyQ created this gist Jan 4, 2023.
    186 changes: 186 additions & 0 deletions Resize.swift
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,186 @@
    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
    }
    }