547 lines
22 KiB
Swift
547 lines
22 KiB
Swift
|
|
//
|
|||
|
|
// UIImage+Ext.swift
|
|||
|
|
// LegendTeam
|
|||
|
|
//
|
|||
|
|
// Created by dong on 2021/12/15.
|
|||
|
|
//
|
|||
|
|
|
|||
|
|
import UIKit
|
|||
|
|
|
|||
|
|
public extension UIImage {
|
|||
|
|
class func withColor(color: UIColor, size: CGSize) -> UIImage {
|
|||
|
|
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
|
|||
|
|
let context = UIGraphicsGetCurrentContext()
|
|||
|
|
context?.setFillColor(color.cgColor)
|
|||
|
|
context?.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height))
|
|||
|
|
let image = UIGraphicsGetImageFromCurrentImageContext()
|
|||
|
|
UIGraphicsEndImageContext()
|
|||
|
|
return image!
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
class func withColor(color: UIColor) -> UIImage {
|
|||
|
|
return UIImage.withColor(color: color, size: CGSize(width: 1, height: 1))
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static func gradientHImageWithSize(size: CGSize, colors: [UIColor]) -> UIImage {
|
|||
|
|
let gradientLayer = CAGradientLayer()
|
|||
|
|
gradientLayer.frame = CGRect(origin: .zero, size: size)
|
|||
|
|
var cgColors : [CGColor] = []
|
|||
|
|
for per in colors {
|
|||
|
|
cgColors.append(per.cgColor)
|
|||
|
|
}
|
|||
|
|
gradientLayer.colors = cgColors
|
|||
|
|
gradientLayer.startPoint = CGPoint(x: 0, y: 1)
|
|||
|
|
gradientLayer.endPoint = CGPoint(x: 0.5, y: 0.5)
|
|||
|
|
gradientLayer.locations = [0, 1]
|
|||
|
|
UIGraphicsBeginImageContext(gradientLayer.bounds.size)
|
|||
|
|
gradientLayer.render(in: UIGraphicsGetCurrentContext()!)
|
|||
|
|
let image = UIGraphicsGetImageFromCurrentImageContext()
|
|||
|
|
UIGraphicsEndImageContext()
|
|||
|
|
return image!
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static func gradientVImageWithSize(size: CGSize, colors: [UIColor]) -> UIImage {
|
|||
|
|
let gradientLayer = CAGradientLayer()
|
|||
|
|
gradientLayer.frame = CGRect(origin: .zero, size: size)
|
|||
|
|
var cgColors : [CGColor] = []
|
|||
|
|
for per in colors {
|
|||
|
|
cgColors.append(per.cgColor)
|
|||
|
|
}
|
|||
|
|
gradientLayer.colors = cgColors
|
|||
|
|
gradientLayer.startPoint = CGPoint(x: 0.5, y: 0)
|
|||
|
|
gradientLayer.endPoint = CGPoint(x: 0.5, y: 1)
|
|||
|
|
gradientLayer.locations = [0, 1]
|
|||
|
|
UIGraphicsBeginImageContext(gradientLayer.bounds.size)
|
|||
|
|
gradientLayer.render(in: UIGraphicsGetCurrentContext()!)
|
|||
|
|
let image = UIGraphicsGetImageFromCurrentImageContext()
|
|||
|
|
UIGraphicsEndImageContext()
|
|||
|
|
return image!
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static func gradientImageWithSize(size: CGSize, colors: [CGColor], startPoint: CGPoint, endPoint: CGPoint) -> UIImage {
|
|||
|
|
let gradientLayer = CAGradientLayer()
|
|||
|
|
gradientLayer.frame = CGRect(origin: .zero, size: size)
|
|||
|
|
gradientLayer.colors = colors
|
|||
|
|
gradientLayer.startPoint = startPoint
|
|||
|
|
gradientLayer.endPoint = endPoint
|
|||
|
|
if(colors.count == 2){
|
|||
|
|
gradientLayer.locations = [0, 1]
|
|||
|
|
}else if(colors.count == 3){
|
|||
|
|
gradientLayer.locations = [0, 0.5, 1]
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
UIGraphicsBeginImageContextWithOptions(size, false, 0.0)
|
|||
|
|
defer { UIGraphicsEndImageContext() }
|
|||
|
|
guard let context = UIGraphicsGetCurrentContext() else { return UIImage() }
|
|||
|
|
gradientLayer.render(in: context)
|
|||
|
|
guard let image = UIGraphicsGetImageFromCurrentImageContext() else { return UIImage() }
|
|||
|
|
|
|||
|
|
return image
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func compress(toByte maxLength: Int) -> UIImage {
|
|||
|
|
var compression: CGFloat = 1
|
|||
|
|
guard var data = jpegData(compressionQuality: compression),
|
|||
|
|
data.count > maxLength else { return self }
|
|||
|
|
|
|||
|
|
dlog("压缩前:\(data.count / 1024) KB")
|
|||
|
|
|
|||
|
|
// Compress by size
|
|||
|
|
var max: CGFloat = 1
|
|||
|
|
var min: CGFloat = 0
|
|||
|
|
for _ in 0 ..< 6 {
|
|||
|
|
compression = (max + min) / 2
|
|||
|
|
data = jpegData(compressionQuality: compression)!
|
|||
|
|
if CGFloat(data.count) < CGFloat(maxLength) * 0.9 {
|
|||
|
|
min = compression
|
|||
|
|
} else if data.count > maxLength {
|
|||
|
|
max = compression
|
|||
|
|
} else {
|
|||
|
|
break
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
dlog("压缩(by size)后1:\(data.count / 1024) KB")
|
|||
|
|
|
|||
|
|
var resultImage = UIImage(data: data)!
|
|||
|
|
if data.count < maxLength { return resultImage }
|
|||
|
|
|
|||
|
|
// Compress by size
|
|||
|
|
var lastDataLength: Int = 0
|
|||
|
|
while data.count > maxLength, data.count != lastDataLength {
|
|||
|
|
lastDataLength = data.count
|
|||
|
|
let ratio = CGFloat(maxLength) / CGFloat(data.count)
|
|||
|
|
let size = CGSize(width: Int(resultImage.size.width * sqrt(ratio)),
|
|||
|
|
height: Int(resultImage.size.height * sqrt(ratio)))
|
|||
|
|
UIGraphicsBeginImageContext(size)
|
|||
|
|
resultImage.draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height))
|
|||
|
|
resultImage = UIGraphicsGetImageFromCurrentImageContext()!
|
|||
|
|
UIGraphicsEndImageContext()
|
|||
|
|
data = resultImage.jpegData(compressionQuality: compression)!
|
|||
|
|
}
|
|||
|
|
dlog("压缩(by size)后2:\(data.count / 1024) KB")
|
|||
|
|
return resultImage
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
static func circleImage(withColor color: UIColor, bounds: CGRect) -> UIImage? {
|
|||
|
|
UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.main.scale)
|
|||
|
|
guard let context = UIGraphicsGetCurrentContext() else { return nil }
|
|||
|
|
|
|||
|
|
context.setStrokeColor(UIColor.clear.cgColor)
|
|||
|
|
context.addEllipse(in: bounds)
|
|||
|
|
context.clip()
|
|||
|
|
context.setFillColor(color.cgColor)
|
|||
|
|
context.fill(bounds)
|
|||
|
|
|
|||
|
|
let image = UIGraphicsGetImageFromCurrentImageContext()
|
|||
|
|
UIGraphicsEndImageContext()
|
|||
|
|
|
|||
|
|
return image
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// properties
|
|||
|
|
public extension UIImage {
|
|||
|
|
/// Size in bytes of UIImage
|
|||
|
|
var bytesSize: Int {
|
|||
|
|
return jpegData(compressionQuality: 1)?.count ?? 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Size in kilo bytes of UIImage
|
|||
|
|
var kilobytesSize: Int {
|
|||
|
|
return (jpegData(compressionQuality: 1)?.count ?? 0) / 1024
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// UIImage with .alwaysOriginal rendering mode.
|
|||
|
|
var original: UIImage {
|
|||
|
|
return withRenderingMode(.alwaysOriginal)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// UIImage with .alwaysTemplate rendering mode.
|
|||
|
|
var template: UIImage {
|
|||
|
|
return withRenderingMode(.alwaysTemplate)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
var pixelSize:CGSize{
|
|||
|
|
return CGSize(width: size.width*scale, height: size.height*scale)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Jpeg: default: 0.8
|
|||
|
|
func imageToBase64(_ image: UIImage, format: ImageFormat = .jpeg(0.8)) -> String? {
|
|||
|
|
var imageData: Data?
|
|||
|
|
|
|||
|
|
switch format {
|
|||
|
|
case .png:
|
|||
|
|
imageData = image.pngData()
|
|||
|
|
case .jpeg(let quality):
|
|||
|
|
imageData = image.jpegData(compressionQuality: quality)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return imageData?.base64EncodedString()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
enum ImageFormat {
|
|||
|
|
case png
|
|||
|
|
case jpeg(CGFloat)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// methods
|
|||
|
|
public extension UIImage {
|
|||
|
|
/// Compressed UIImage from original UIImage.
|
|||
|
|
///
|
|||
|
|
/// - Parameter quality: The quality of the resulting JPEG image, expressed as a value from 0.0 to 1.0. The value 0.0 represents the maximum compression (or lowest quality) while the value 1.0 represents the least compression (or best quality), (default is 0.5).
|
|||
|
|
/// - Returns: optional UIImage (if applicable).
|
|||
|
|
func compressed(quality: CGFloat = 0.5) -> UIImage? {
|
|||
|
|
guard let data = jpegData(compressionQuality: quality) else { return nil }
|
|||
|
|
return UIImage(data: data)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Compressed UIImage data from original UIImage.
|
|||
|
|
///
|
|||
|
|
/// - Parameter quality: The quality of the resulting JPEG image, expressed as a value from 0.0 to 1.0. The value 0.0 represents the maximum compression (or lowest quality) while the value 1.0 represents the least compression (or best quality), (default is 0.5).
|
|||
|
|
/// - Returns: optional Data (if applicable).
|
|||
|
|
func compressedData(quality: CGFloat = 0.5) -> Data? {
|
|||
|
|
return jpegData(compressionQuality: quality)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// UIImage Cropped to CGRect.
|
|||
|
|
///
|
|||
|
|
/// - Parameter rect: CGRect to crop UIImage to.
|
|||
|
|
/// - Returns: cropped UIImage
|
|||
|
|
func cropped(to rect: CGRect) -> UIImage {
|
|||
|
|
guard rect.size.width <= size.width && rect.size.height <= size.height else { return self }
|
|||
|
|
guard let image: CGImage = cgImage?.cropping(to: rect) else { return self }
|
|||
|
|
return UIImage(cgImage: image)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func cropImageTop(with scale: CGFloat) -> UIImage? {
|
|||
|
|
// Original image
|
|||
|
|
guard let image = self as UIImage? else { return nil }
|
|||
|
|
|
|||
|
|
// Get CGImage
|
|||
|
|
guard let imageRef = image.cgImage else { return nil }
|
|||
|
|
|
|||
|
|
// Calculate height
|
|||
|
|
var height = image.size.width * scale
|
|||
|
|
if height > image.size.height {
|
|||
|
|
height = image.size.height
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Define crop area (full width, dynamic height from top)
|
|||
|
|
let cutArea = CGRect(x: 0, y: 0, width: image.size.width, height: height)
|
|||
|
|
|
|||
|
|
// Crop the image
|
|||
|
|
guard let cgImage = imageRef.cropping(to: cutArea) else { return nil }
|
|||
|
|
|
|||
|
|
// Create new UIImage from cropped CGImage
|
|||
|
|
let cutImage = UIImage(cgImage: cgImage)
|
|||
|
|
|
|||
|
|
return cutImage
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// UIImage scaled to height with respect to aspect ratio.
|
|||
|
|
///
|
|||
|
|
/// - Parameters:
|
|||
|
|
/// - toHeight: new height.
|
|||
|
|
/// - opaque: flag indicating whether the bitmap is opaque.
|
|||
|
|
/// - Returns: optional scaled UIImage (if applicable).
|
|||
|
|
func scaled(toHeight: CGFloat, opaque: Bool = false) -> UIImage? {
|
|||
|
|
let scale = toHeight / size.height
|
|||
|
|
let newWidth = size.width * scale
|
|||
|
|
UIGraphicsBeginImageContextWithOptions(CGSize(width: newWidth, height: toHeight), opaque, UIScreen.main.scale) //
|
|||
|
|
draw(in: CGRect(x: 0, y: 0, width: newWidth, height: toHeight))
|
|||
|
|
let newImage = UIGraphicsGetImageFromCurrentImageContext()
|
|||
|
|
UIGraphicsEndImageContext()
|
|||
|
|
return newImage
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// UIImage scaled to width with respect to aspect ratio.
|
|||
|
|
///
|
|||
|
|
/// - Parameters:
|
|||
|
|
/// - toWidth: new width.
|
|||
|
|
/// - opaque: flag indicating whether the bitmap is opaque.
|
|||
|
|
/// - Returns: optional scaled UIImage (if applicable).
|
|||
|
|
func scaled(toWidth: CGFloat, opaque: Bool = false) -> UIImage? {
|
|||
|
|
let scale = toWidth / size.width
|
|||
|
|
let newHeight = size.height * scale
|
|||
|
|
UIGraphicsBeginImageContextWithOptions(CGSize(width: toWidth, height: newHeight), opaque, self.scale)
|
|||
|
|
draw(in: CGRect(x: 0, y: 0, width: toWidth, height: newHeight))
|
|||
|
|
let newImage = UIGraphicsGetImageFromCurrentImageContext()
|
|||
|
|
UIGraphicsEndImageContext()
|
|||
|
|
return newImage
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Creates a copy of the receiver rotated by the given angle.
|
|||
|
|
///
|
|||
|
|
/// // Rotate the image by 180°
|
|||
|
|
/// image.rotated(by: Measurement(value: 180, unit: .degrees))
|
|||
|
|
///
|
|||
|
|
/// - Parameter angle: The angle measurement by which to rotate the image.
|
|||
|
|
/// - Returns: A new image rotated by the given angle.
|
|||
|
|
@available(iOS 10.0, tvOS 10.0, watchOS 3.0, *)
|
|||
|
|
func rotated(by angle: Measurement<UnitAngle>) -> UIImage? {
|
|||
|
|
let radians = CGFloat(angle.converted(to: .radians).value)
|
|||
|
|
|
|||
|
|
let destRect = CGRect(origin: .zero, size: size)
|
|||
|
|
.applying(CGAffineTransform(rotationAngle: radians))
|
|||
|
|
let roundedDestRect = CGRect(x: destRect.origin.x.rounded(),
|
|||
|
|
y: destRect.origin.y.rounded(),
|
|||
|
|
width: destRect.width.rounded(),
|
|||
|
|
height: destRect.height.rounded())
|
|||
|
|
|
|||
|
|
UIGraphicsBeginImageContext(roundedDestRect.size)
|
|||
|
|
guard let contextRef = UIGraphicsGetCurrentContext() else { return nil }
|
|||
|
|
|
|||
|
|
contextRef.translateBy(x: roundedDestRect.width / 2, y: roundedDestRect.height / 2)
|
|||
|
|
contextRef.rotate(by: radians)
|
|||
|
|
|
|||
|
|
draw(in: CGRect(origin: CGPoint(x: -size.width / 2,
|
|||
|
|
y: -size.height / 2),
|
|||
|
|
size: size))
|
|||
|
|
|
|||
|
|
let newImage = UIGraphicsGetImageFromCurrentImageContext()
|
|||
|
|
UIGraphicsEndImageContext()
|
|||
|
|
return newImage
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Creates a copy of the receiver rotated by the given angle (in radians).
|
|||
|
|
///
|
|||
|
|
/// // Rotate the image by 180°
|
|||
|
|
/// image.rotated(by: .pi)
|
|||
|
|
///
|
|||
|
|
/// - Parameter radians: The angle, in radians, by which to rotate the image.
|
|||
|
|
/// - Returns: A new image rotated by the given angle.
|
|||
|
|
func rotated(by radians: CGFloat) -> UIImage? {
|
|||
|
|
let destRect = CGRect(origin: .zero, size: size)
|
|||
|
|
.applying(CGAffineTransform(rotationAngle: radians))
|
|||
|
|
let roundedDestRect = CGRect(x: destRect.origin.x.rounded(),
|
|||
|
|
y: destRect.origin.y.rounded(),
|
|||
|
|
width: destRect.width.rounded(),
|
|||
|
|
height: destRect.height.rounded())
|
|||
|
|
|
|||
|
|
UIGraphicsBeginImageContext(roundedDestRect.size)
|
|||
|
|
guard let contextRef = UIGraphicsGetCurrentContext() else { return nil }
|
|||
|
|
|
|||
|
|
contextRef.translateBy(x: roundedDestRect.width / 2, y: roundedDestRect.height / 2)
|
|||
|
|
contextRef.rotate(by: radians)
|
|||
|
|
|
|||
|
|
draw(in: CGRect(origin: CGPoint(x: -size.width / 2,
|
|||
|
|
y: -size.height / 2),
|
|||
|
|
size: size))
|
|||
|
|
|
|||
|
|
let newImage = UIGraphicsGetImageFromCurrentImageContext()
|
|||
|
|
UIGraphicsEndImageContext()
|
|||
|
|
return newImage
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// UIImage filled with color
|
|||
|
|
///
|
|||
|
|
/// - Parameter color: color to fill image with.
|
|||
|
|
/// - Returns: UIImage filled with given color.
|
|||
|
|
func filled(withColor color: UIColor) -> UIImage {
|
|||
|
|
#if !os(watchOS)
|
|||
|
|
if #available(iOS 10, tvOS 10, *) {
|
|||
|
|
let format = UIGraphicsImageRendererFormat()
|
|||
|
|
format.scale = scale
|
|||
|
|
let renderer = UIGraphicsImageRenderer(size: size, format: format)
|
|||
|
|
return renderer.image { context in
|
|||
|
|
color.setFill()
|
|||
|
|
context.fill(CGRect(origin: .zero, size: size))
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
#endif
|
|||
|
|
|
|||
|
|
UIGraphicsBeginImageContextWithOptions(size, false, scale)
|
|||
|
|
color.setFill()
|
|||
|
|
guard let context = UIGraphicsGetCurrentContext() else { return self }
|
|||
|
|
|
|||
|
|
context.translateBy(x: 0, y: size.height)
|
|||
|
|
context.scaleBy(x: 1.0, y: -1.0)
|
|||
|
|
context.setBlendMode(CGBlendMode.normal)
|
|||
|
|
|
|||
|
|
let rect = CGRect(x: 0, y: 0, width: size.width, height: size.height)
|
|||
|
|
guard let mask = cgImage else { return self }
|
|||
|
|
context.clip(to: rect, mask: mask)
|
|||
|
|
context.fill(rect)
|
|||
|
|
|
|||
|
|
let newImage = UIGraphicsGetImageFromCurrentImageContext()!
|
|||
|
|
UIGraphicsEndImageContext()
|
|||
|
|
return newImage
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// UIImage tinted with color
|
|||
|
|
///
|
|||
|
|
/// - Parameters:
|
|||
|
|
/// - color: color to tint image with.
|
|||
|
|
/// - blendMode: how to blend the tint
|
|||
|
|
/// - Returns: UIImage tinted with given color.
|
|||
|
|
func tint(_ color: UIColor, blendMode: CGBlendMode, alpha: CGFloat = 1.0) -> UIImage {
|
|||
|
|
let drawRect = CGRect(origin: .zero, size: size)
|
|||
|
|
|
|||
|
|
#if !os(watchOS)
|
|||
|
|
if #available(iOS 10.0, tvOS 10.0, *) {
|
|||
|
|
let format = UIGraphicsImageRendererFormat()
|
|||
|
|
format.scale = scale
|
|||
|
|
return UIGraphicsImageRenderer(size: size, format: format).image { context in
|
|||
|
|
color.setFill()
|
|||
|
|
context.fill(drawRect)
|
|||
|
|
draw(in: drawRect, blendMode: blendMode, alpha: alpha)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
#endif
|
|||
|
|
|
|||
|
|
UIGraphicsBeginImageContextWithOptions(size, false, scale)
|
|||
|
|
defer {
|
|||
|
|
UIGraphicsEndImageContext()
|
|||
|
|
}
|
|||
|
|
let context = UIGraphicsGetCurrentContext()
|
|||
|
|
color.setFill()
|
|||
|
|
context?.fill(drawRect)
|
|||
|
|
draw(in: drawRect, blendMode: blendMode, alpha: alpha)
|
|||
|
|
return UIGraphicsGetImageFromCurrentImageContext()!
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// UImage with background color
|
|||
|
|
///
|
|||
|
|
/// - Parameters:
|
|||
|
|
/// - backgroundColor: Color to use as background color
|
|||
|
|
/// - Returns: UIImage with a background color that is visible where alpha < 1
|
|||
|
|
func withBackgroundColor(_ backgroundColor: UIColor) -> UIImage {
|
|||
|
|
#if !os(watchOS)
|
|||
|
|
if #available(iOS 10.0, tvOS 10.0, *) {
|
|||
|
|
let format = UIGraphicsImageRendererFormat()
|
|||
|
|
format.scale = scale
|
|||
|
|
return UIGraphicsImageRenderer(size: size, format: format).image { context in
|
|||
|
|
backgroundColor.setFill()
|
|||
|
|
context.fill(context.format.bounds)
|
|||
|
|
draw(at: .zero)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
#endif
|
|||
|
|
|
|||
|
|
UIGraphicsBeginImageContextWithOptions(size, false, scale)
|
|||
|
|
defer { UIGraphicsEndImageContext() }
|
|||
|
|
|
|||
|
|
backgroundColor.setFill()
|
|||
|
|
UIRectFill(CGRect(origin: .zero, size: size))
|
|||
|
|
draw(at: .zero)
|
|||
|
|
|
|||
|
|
return UIGraphicsGetImageFromCurrentImageContext()!
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// UIImage with rounded corners
|
|||
|
|
///
|
|||
|
|
/// - Parameters:
|
|||
|
|
/// - radius: corner radius (optional), resulting image will be round if unspecified
|
|||
|
|
/// - Returns: UIImage with all corners rounded
|
|||
|
|
func withRoundedCorners(radius: CGFloat? = nil) -> UIImage? {
|
|||
|
|
let maxRadius = min(size.width, size.height) / 2
|
|||
|
|
let cornerRadius: CGFloat
|
|||
|
|
if let radius = radius, radius > 0 && radius <= maxRadius {
|
|||
|
|
cornerRadius = radius
|
|||
|
|
} else {
|
|||
|
|
cornerRadius = maxRadius
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
UIGraphicsBeginImageContextWithOptions(size, false, scale)
|
|||
|
|
|
|||
|
|
let rect = CGRect(origin: .zero, size: size)
|
|||
|
|
UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).addClip()
|
|||
|
|
draw(in: rect)
|
|||
|
|
|
|||
|
|
let image = UIGraphicsGetImageFromCurrentImageContext()
|
|||
|
|
UIGraphicsEndImageContext()
|
|||
|
|
return image
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Base 64 encoded PNG data of the image.
|
|||
|
|
///
|
|||
|
|
/// - returns: Base 64 encoded PNG data of the image as a String.
|
|||
|
|
func pngBase64String() -> String? {
|
|||
|
|
return pngData()?.base64EncodedString()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Base 64 encoded JPEG data of the image.
|
|||
|
|
///
|
|||
|
|
/// - parameter compressionQuality: The quality of the resulting JPEG image, expressed as a value from 0.0 to 1.0. The value 0.0 represents the maximum compression (or lowest quality) while the value 1.0 represents the least compression (or best quality).
|
|||
|
|
/// - returns: Base 64 encoded JPEG data of the image as a String.
|
|||
|
|
func jpegBase64String(compressionQuality: CGFloat) -> String? {
|
|||
|
|
return jpegData(compressionQuality: compressionQuality)?.base64EncodedString()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// 创建一个可拉伸的UIImage,指定水平和垂直拉伸位置
|
|||
|
|
/// - Parameters:
|
|||
|
|
/// - horizontalRatio: 水平拉伸点比例(0.0到1.0),表示拉伸点的相对位置
|
|||
|
|
/// - verticalRatio: 垂直拉伸点比例(0.0到1.0),表示拉伸点的相对位置
|
|||
|
|
/// - Returns: 可拉伸的UIImage
|
|||
|
|
func stretchableImage(horizontalRatio: CGFloat, verticalRatio: CGFloat) -> UIImage {
|
|||
|
|
// 确保比例在0.0到1.0之间
|
|||
|
|
let clampedHorizontalRatio = max(0.0, min(1.0, horizontalRatio))
|
|||
|
|
let clampedVerticalRatio = max(0.0, min(1.0, verticalRatio))
|
|||
|
|
|
|||
|
|
// 计算拉伸区域
|
|||
|
|
let width = self.size.width
|
|||
|
|
let height = self.size.height
|
|||
|
|
|
|||
|
|
let leftCap = floor(width * clampedHorizontalRatio)
|
|||
|
|
let topCap = floor(height * clampedVerticalRatio)
|
|||
|
|
|
|||
|
|
// 创建可拉伸图片
|
|||
|
|
return self.resizableImage(withCapInsets: UIEdgeInsets(
|
|||
|
|
top: topCap,
|
|||
|
|
left: leftCap,
|
|||
|
|
bottom: height - topCap - 1,
|
|||
|
|
right: width - leftCap - 1
|
|||
|
|
), resizingMode: .stretch)
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
public extension UIImage {
|
|||
|
|
/// Create UIImage from color and size.
|
|||
|
|
///
|
|||
|
|
/// - Parameters:
|
|||
|
|
/// - color: image fill color.
|
|||
|
|
/// - size: image size.
|
|||
|
|
convenience init(color: UIColor, size: CGSize) {
|
|||
|
|
UIGraphicsBeginImageContextWithOptions(size, false, 1)
|
|||
|
|
|
|||
|
|
defer {
|
|||
|
|
UIGraphicsEndImageContext()
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
color.setFill()
|
|||
|
|
UIRectFill(CGRect(origin: .zero, size: size))
|
|||
|
|
|
|||
|
|
guard let aCgImage = UIGraphicsGetImageFromCurrentImageContext()?.cgImage else {
|
|||
|
|
self.init()
|
|||
|
|
return
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
self.init(cgImage: aCgImage)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Create a new image from a base 64 string.
|
|||
|
|
///
|
|||
|
|
/// - Parameters:
|
|||
|
|
/// - base64String: a base-64 `String`, representing the image
|
|||
|
|
/// - scale: The scale factor to assume when interpreting the image data created from the base-64 string. Applying a scale factor of 1.0 results in an image whose size matches the pixel-based dimensions of the image. Applying a different scale factor changes the size of the image as reported by the `size` property.
|
|||
|
|
convenience init?(base64String: String, scale: CGFloat = 1.0) {
|
|||
|
|
guard let data = Data(base64Encoded: base64String) else { return nil }
|
|||
|
|
self.init(data: data, scale: scale)
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/// Create a new image from a URL
|
|||
|
|
///
|
|||
|
|
/// - Important:
|
|||
|
|
/// Use this method to convert data:// URLs to UIImage objects.
|
|||
|
|
/// Don't use this synchronous initializer to request network-based URLs. For network-based URLs, this method can block the current thread for tens of seconds on a slow network, resulting in a poor user experience, and in iOS, may cause your app to be terminated.
|
|||
|
|
/// Instead, for non-file URLs, consider using this in an asynchronous way, using `dataTask(with:completionHandler:)` method of the URLSession class or a library such as `AlamofireImage`, `Kingfisher`, `SDWebImage`, or others to perform asynchronous network image loading.
|
|||
|
|
/// - Parameters:
|
|||
|
|
/// - url: a `URL`, representing the image location
|
|||
|
|
/// - scale: The scale factor to assume when interpreting the image data created from the URL. Applying a scale factor of 1.0 results in an image whose size matches the pixel-based dimensions of the image. Applying a different scale factor changes the size of the image as reported by the `size` property.
|
|||
|
|
convenience init?(url: URL, scale: CGFloat = 1.0) throws {
|
|||
|
|
let data = try Data(contentsOf: url)
|
|||
|
|
self.init(data: data, scale: scale)
|
|||
|
|
}
|
|||
|
|
}
|