xcodeswiftcgbitmapcontext

how to resize a bitmap on iOS


I want to resize a bitmap for a project that I am doing. I managed to do this by converting the cgbitmapcontextref to a pixel array and then manipulating the array of pixels and then generated a new image from the manipulated pixel data. This way is extremely cumbersome.

I want to know if there is any other way to resize a CGBitmapContextRef. Thanks.


Solution

  • If you're not wed to CGBitmapContextRef, here are some UIKit-based simple image resizing routines. This extension offers resizes images by either cropping, scaling, aspect filling, and aspect fitting (analogous to a few of the main content modes that UIImageView offers).

    //
    //  UIImage+Resize.swift
    //
    //  Image resizing extension
    //
    //  Created by Robert Ryan on 19-May-11.
    //  Ported to Swift by Robert Ryan on 12-Feb-15.
    //  Modified for Swift 2 by Robert Ryan on 14-Oct-15
    //  Modified for Swift 3 by Robert Ryan on 26-May-17
    //  Modified for Swift 4 by Robert Ryan on 15-Feb-19
    //
    //  Inspired by http://ofcodeandmen.poltras.com/2008/10/30/undocumented-uiimage-resizing/
    //  but adjusted to support AspectFill and AspectFit modes.
    //
    //  Copyright (c) 2015 Robert M. Ryan. All rights reserved.
    //
    //  This work by Robert M. Ryan is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.
    //  http://creativecommons.org/licenses/by-sa/4.0/
    
    import UIKit
    
    extension UIImage {
    
        /// Resize the image to be the required size, stretching it as needed.
        ///
        /// - parameter newSize:      The new size of the image.
        /// - parameter contentMode:  The `UIView.ContentMode` to be applied when resizing image.
        ///                           Either `.scaleToFill`, `.scaleAspectFill`, or `.scaleAspectFit`.
        ///
        /// - returns:                Return `UIImage` of resized image.
    
        func scaled(to newSize: CGSize, contentMode: UIView.ContentMode = .scaleToFill) -> UIImage? {
            switch contentMode {
            case .scaleToFill:
                return filled(to: newSize)
    
            case .scaleAspectFill, .scaleAspectFit:
                let horizontalRatio = size.width  / newSize.width
                let verticalRatio   = size.height / newSize.height
    
                let ratio: CGFloat!
                if contentMode == .scaleAspectFill {
                    ratio = min(horizontalRatio, verticalRatio)
                } else {
                    ratio = max(horizontalRatio, verticalRatio)
                }
    
                let sizeForAspectScale = CGSize(width: size.width / ratio, height: size.height / ratio)
                let image = filled(to: sizeForAspectScale)
                let doesAspectFitNeedCropping = contentMode == .scaleAspectFit && (newSize.width > sizeForAspectScale.width || newSize.height > sizeForAspectScale.height)
                if contentMode == .scaleAspectFill || doesAspectFitNeedCropping {
                    let subRect = CGRect(
                        x: floor((sizeForAspectScale.width - newSize.width) / 2.0),
                        y: floor((sizeForAspectScale.height - newSize.height) / 2.0),
                        width: newSize.width,
                        height: newSize.height)
                    return image?.cropped(to: subRect)
                }
                return image
    
            default:
                return nil
            }
        }
    
        /// Resize the image to be the required size, stretching it as needed.
        ///
        /// - parameter newSize:   The new size of the image.
        ///
        /// - returns:             Resized `UIImage` of resized image.
    
        func filled(to newSize: CGSize) -> UIImage? {
            let format = UIGraphicsImageRendererFormat()
            format.opaque = false
            format.scale = scale
    
            return UIGraphicsImageRenderer(size: newSize, format: format).image { _ in
                draw(in: CGRect(origin: .zero, size: newSize))
            }
        }
    
        /// Crop the image to be the required size.
        ///
        /// - parameter bounds:    The bounds to which the new image should be cropped.
        ///
        /// - returns:             Cropped `UIImage`.
    
        func cropped(to bounds: CGRect) -> UIImage? {
            // if bounds is entirely within image, do simple CGImage `cropping` ...
    
            if CGRect(origin: .zero, size: size).contains(bounds), imageOrientation == .up, let cgImage = cgImage {
                return cgImage.cropping(to: bounds * scale).flatMap {
                    UIImage(cgImage: $0, scale: scale, orientation: imageOrientation)
                }
            }
    
            // ... otherwise, manually render whole image, only drawing what we need
    
            let format = UIGraphicsImageRendererFormat()
            format.opaque = false
            format.scale = scale
    
            return UIGraphicsImageRenderer(size: bounds.size, format: format).image { _ in
                let origin = CGPoint(x: -bounds.minX, y: -bounds.minY)
                draw(in: CGRect(origin: origin, size: size))
            }
        }
    
        /// Resize the image to fill the rectange of the specified size, preserving the aspect ratio, trimming if needed.
        ///
        /// - parameter newSize:   The new size of the image.
        ///
        /// - returns:             Return `UIImage` of resized image.
    
        func scaledAspectFill(to newSize: CGSize) -> UIImage? {
            return scaled(to: newSize, contentMode: .scaleAspectFill)
        }
    
        /// Resize the image to fit within the required size, preserving the aspect ratio, with no trimming taking place.
        ///
        /// - parameter newSize:   The new size of the image.
        ///
        /// - returns:             Return `UIImage` of resized image.
    
        func scaledAspectFit(to newSize: CGSize) -> UIImage? {
            return scaled(to: newSize, contentMode: .scaleAspectFit)
        }
    
    }
    
    extension CGSize {
        static func * (lhs: CGSize, rhs: CGFloat) -> CGSize {
            return CGSize(width: lhs.width * rhs, height: lhs.height * rhs)
        }
    }
    
    extension CGPoint {
        static func * (lhs: CGPoint, rhs: CGFloat) -> CGPoint {
            return CGPoint(x: lhs.x * rhs, y: lhs.y * rhs)
        }
    }
    
    extension CGRect {
        static func * (lhs: CGRect, rhs: CGFloat) -> CGRect {
            return CGRect(origin: lhs.origin * rhs, size: lhs.size * rhs)
        }
    }
    

    For Swift 2 rendition, see previous revision of this answer.