You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
session-ios/SessionUtilitiesKit/Utilities/UIImage+Utilities.swift

188 lines
8.6 KiB
Swift

// Copyright © 2024 Rangeproof Pty Ltd. All rights reserved.
import UIKit.UIImage
public extension UIImage {
func normalizedImage() -> UIImage {
guard imageOrientation != .up else { return self }
// The actual resize: draw the image on a new context, applying a transform matrix
let bounds: CGRect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
let format = UIGraphicsImageRendererFormat()
format.scale = self.scale
format.opaque = false
// Note: We use the UIImage.draw function here instead of using the CGContext because UIImage
// automatically deals with orientations so we don't have to
return UIGraphicsImageRenderer(bounds: bounds, format: format).image { _ in
self.draw(in: bounds)
}
}
/// This function can be used to resize an image to a different size, it **should not** be used within the UI for rendering smaller
/// images as it's fairly inefficient (instead the image should be contained within another view and sized explicitly that way)
func resized(toFillPixelSize dstSize: CGSize) -> UIImage {
let normalized: UIImage = self.normalizedImage()
guard
let normalizedRef: CGImage = normalized.cgImage,
let imgRef: CGImage = self.cgImage
else { return self }
// Get the size in pixels, not points
let srcSize: CGSize = CGSize(width: normalizedRef.width, height: normalizedRef.height)
let widthRatio: CGFloat = (srcSize.width / srcSize.height)
let heightRatio: CGFloat = (srcSize.height / srcSize.height)
let drawRect: CGRect = {
guard widthRatio <= heightRatio else {
let targetWidth: CGFloat = (dstSize.height * srcSize.width / srcSize.height)
return CGRect(
x: (targetWidth - dstSize.width) * -0.5,
y: 0,
width: targetWidth,
height: dstSize.height
)
}
let targetHeight: CGFloat = (dstSize.width * srcSize.height / srcSize.width)
return CGRect(
x: 0,
y: (targetHeight - dstSize.height) * -0.5,
width: dstSize.width,
height: targetHeight
)
}()
let bounds: CGRect = CGRect(x: 0, y: 0, width: dstSize.width, height: dstSize.height)
let format = UIGraphicsImageRendererFormat()
format.scale = 1 // We are specifying a specific pixel size rather than a point size
format.opaque = false
let renderer: UIGraphicsImageRenderer = UIGraphicsImageRenderer(bounds: bounds, format: format)
return renderer.image { rendererContext in
rendererContext.cgContext.interpolationQuality = .high
// we use srcSize (and not dstSize) as the size to specify is in user space (and we use the CTM to apply a
// scaleRatio)
rendererContext.cgContext.draw(imgRef, in: drawRect, byTiling: false)
}
}
/// This function can be used to resize an image to a different size, it **should not** be used within the UI for rendering smaller
/// images as it's fairly inefficient (instead the image should be contained within another view and sized explicitly that way)
func resized(maxDimensionPoints: CGFloat) -> UIImage? {
guard let imgRef: CGImage = self.cgImage else { return nil }
let originalSize: CGSize = self.size
let maxOriginalDimensionPoints: CGFloat = max(originalSize.width, originalSize.height)
guard originalSize.width > 0 && originalSize.height > 0 else { return nil }
// Don't bother scaling an image that is already smaller than the max dimension.
guard maxOriginalDimensionPoints > maxDimensionPoints else { return self }
let thumbnailSize: CGSize = {
guard originalSize.width <= originalSize.height else {
return CGSize(
width: maxDimensionPoints,
height: round(maxDimensionPoints * originalSize.height / originalSize.width)
)
}
return CGSize(
width: round(maxDimensionPoints * originalSize.width / originalSize.height),
height: maxDimensionPoints
)
}()
guard thumbnailSize.width > 0 && thumbnailSize.height > 0 else { return nil }
// the below values are regardless of orientation : for UIImages from Camera, width>height (landscape)
//
// Note: Not equivalent to self.size (which is dependant on the imageOrientation)!
let srcSize: CGSize = CGSize(width: imgRef.width, height: imgRef.height)
var dstSize: CGSize = thumbnailSize
// Don't resize if we already meet the required destination size
guard dstSize != srcSize else { return self }
let scaleRatio: CGFloat = (dstSize.width / srcSize.width)
let orient: UIImage.Orientation = self.imageOrientation
var transform: CGAffineTransform = .identity
switch orient {
case .up: break // EXIF = 1
case .upMirrored: // EXIF = 2
transform = CGAffineTransform(translationX: srcSize.width, y: 0)
.scaledBy(x: -1, y: 1)
case .down: // EXIF = 3
transform = CGAffineTransform(translationX: srcSize.width, y: srcSize.height)
.rotated(by: CGFloat.pi)
case .downMirrored: // EXIF = 4
transform = CGAffineTransform(translationX: 0, y: srcSize.height)
.scaledBy(x: 1, y: -1)
case .leftMirrored: // EXIF = 5
dstSize = CGSize(width: dstSize.height, height: dstSize.width)
transform = CGAffineTransform(translationX: srcSize.height, y: srcSize.width)
.scaledBy(x: -1, y: 1)
.rotated(by: (3 * (CGFloat.pi / 2)))
case .left: // EXIF = 6
dstSize = CGSize(width: dstSize.height, height: dstSize.width)
transform = CGAffineTransform(translationX: 0, y: srcSize.width)
.scaledBy(x: -1, y: 1)
.rotated(by: (3 * (CGFloat.pi / 2)))
case .rightMirrored: // EXIF = 7
dstSize = CGSize(width: dstSize.height, height: dstSize.width)
transform = CGAffineTransform(scaleX: -1, y: 1)
.rotated(by: (CGFloat.pi / 2))
case .right: // EXIF = 8
dstSize = CGSize(width: dstSize.height, height: dstSize.width)
transform = CGAffineTransform(translationX: srcSize.height, y: 0)
.rotated(by: (CGFloat.pi / 2))
@unknown default: return nil
}
// The actual resize: draw the image on a new context, applying a transform matrix
let bounds: CGRect = CGRect(x: 0, y: 0, width: dstSize.width, height: dstSize.height)
let format = UIGraphicsImageRendererFormat()
format.scale = self.scale
format.opaque = false
let renderer: UIGraphicsImageRenderer = UIGraphicsImageRenderer(bounds: bounds, format: format)
return renderer.image { rendererContext in
rendererContext.cgContext.interpolationQuality = .high
switch orient {
case .right, .left:
rendererContext.cgContext.scaleBy(x: -scaleRatio, y: scaleRatio)
rendererContext.cgContext.translateBy(x: -srcSize.height, y: 0)
default:
rendererContext.cgContext.scaleBy(x: scaleRatio, y: -scaleRatio)
rendererContext.cgContext.translateBy(x: 0, y: -srcSize.height)
}
rendererContext.cgContext.concatenate(transform)
// we use srcSize (and not dstSize) as the size to specify is in user space (and we use the CTM to apply a
// scaleRatio)
rendererContext.cgContext.draw(
imgRef,
in: CGRect(x: 0, y: 0, width: srcSize.width, height: srcSize.height),
byTiling: false
)
}
}
}