mirror of https://github.com/oxen-io/session-ios
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.
188 lines
8.6 KiB
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
|
|
)
|
|
}
|
|
}
|
|
}
|