226 lines
6.4 KiB
Swift
226 lines
6.4 KiB
Swift
//
|
|
// RSImage.swift
|
|
// RSCore
|
|
//
|
|
// Created by Maurice Parker on 4/11/19.
|
|
// Copyright © 2019 Ranchero Software. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
|
|
#if os(macOS)
|
|
import AppKit
|
|
public typealias RSImage = NSImage
|
|
#endif
|
|
|
|
#if os(iOS)
|
|
import UIKit
|
|
public typealias RSImage = UIImage
|
|
#endif
|
|
|
|
public extension RSImage {
|
|
|
|
#if os(macOS)
|
|
static let maxScreenScale = CGFloat(2)
|
|
#endif
|
|
|
|
#if os(iOS)
|
|
static let maxScreenScale = CGFloat(3)
|
|
#endif
|
|
|
|
/// Create a colored image from the source image using a specified color.
|
|
///
|
|
/// - Parameter color: The color with which to fill the mask image.
|
|
/// - Returns: A new masked image.
|
|
func maskWithColor(color: CGColor) -> RSImage? {
|
|
|
|
#if os(macOS)
|
|
guard let maskImage = cgImage(forProposedRect: nil, context: nil, hints: nil) else { return nil }
|
|
#else
|
|
guard let maskImage = cgImage else { return nil }
|
|
#endif
|
|
|
|
let width = size.width
|
|
let height = size.height
|
|
let bounds = CGRect(x: 0, y: 0, width: width, height: height)
|
|
|
|
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
|
let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.premultipliedLast.rawValue)
|
|
let context = CGContext(data: nil, width: Int(width), height: Int(height), bitsPerComponent: 8, bytesPerRow: 0, space: colorSpace, bitmapInfo: bitmapInfo.rawValue)!
|
|
|
|
context.clip(to: bounds, mask: maskImage)
|
|
context.setFillColor(color)
|
|
context.fill(bounds)
|
|
|
|
if let cgImage = context.makeImage() {
|
|
#if os(macOS)
|
|
let coloredImage = RSImage(cgImage: cgImage, size: CGSize(width: cgImage.width, height: cgImage.height))
|
|
#else
|
|
let coloredImage = RSImage(cgImage: cgImage)
|
|
#endif
|
|
return coloredImage
|
|
} else {
|
|
return nil
|
|
}
|
|
|
|
}
|
|
|
|
#if os(iOS)
|
|
/// Tint an image.
|
|
///
|
|
/// - Parameter color: The color to use to tint the image.
|
|
/// - Returns: The tinted image.
|
|
func tinted(color: UIColor) -> UIImage? {
|
|
let image = withRenderingMode(.alwaysTemplate)
|
|
let imageView = UIImageView(image: image)
|
|
imageView.tintColor = color
|
|
|
|
UIGraphicsBeginImageContextWithOptions(image.size, false, 0.0)
|
|
if let context = UIGraphicsGetCurrentContext() {
|
|
imageView.layer.render(in: context)
|
|
let tintedImage = UIGraphicsGetImageFromCurrentImageContext()
|
|
UIGraphicsEndImageContext()
|
|
return tintedImage
|
|
} else {
|
|
return self
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/// Returns a data representation of the image.
|
|
///
|
|
/// The resultant data is TIFF data on macOS, and PNG data on iOS.
|
|
/// - Returns: Data representing the image.
|
|
func dataRepresentation() -> Data? {
|
|
#if os(macOS)
|
|
return tiffRepresentation
|
|
#else
|
|
return pngData()
|
|
#endif
|
|
}
|
|
|
|
/// Asynchronously initializes an image from data.
|
|
///
|
|
/// - Parameters:
|
|
/// - data: The data object containing the image data.
|
|
/// - imageResultBlock: The closure to call when the image has been initialized.
|
|
static func image(with data: Data, imageResultBlock: @escaping ImageResultBlock) {
|
|
DispatchQueue.global().async {
|
|
let image = RSImage(data: data)
|
|
DispatchQueue.main.async {
|
|
imageResultBlock(image)
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Create a scaled image from image data.
|
|
///
|
|
/// - Note: the returned image may be larger than `maxPixelSize`, but not more than `maxPixelSize * 2`.
|
|
/// - Parameters:
|
|
/// - data: The data object containing the image data.
|
|
/// - maxPixelSize: The maximum dimension of the image.
|
|
static func scaleImage(_ data: Data, maxPixelSize: Int) -> CGImage? {
|
|
|
|
guard let imageSource = CGImageSourceCreateWithData(data as CFData, nil) else {
|
|
return nil
|
|
}
|
|
|
|
let numberOfImages = CGImageSourceGetCount(imageSource)
|
|
|
|
// If the image size matches exactly, then return it.
|
|
for i in 0..<numberOfImages {
|
|
|
|
guard let cfImageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, i, nil) else {
|
|
continue
|
|
}
|
|
|
|
let imageProperties = cfImageProperties as NSDictionary
|
|
|
|
guard let imagePixelWidth = imageProperties[kCGImagePropertyPixelWidth] as? NSNumber else {
|
|
continue
|
|
}
|
|
if imagePixelWidth.intValue != maxPixelSize {
|
|
continue
|
|
}
|
|
|
|
guard let imagePixelHeight = imageProperties[kCGImagePropertyPixelHeight] as? NSNumber else {
|
|
continue
|
|
}
|
|
if imagePixelHeight.intValue != maxPixelSize {
|
|
continue
|
|
}
|
|
|
|
return CGImageSourceCreateImageAtIndex(imageSource, i, nil)
|
|
}
|
|
|
|
// If image height > maxPixelSize, but <= maxPixelSize * 2, then return it.
|
|
for i in 0..<numberOfImages {
|
|
|
|
guard let cfImageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, i, nil) else {
|
|
continue
|
|
}
|
|
|
|
let imageProperties = cfImageProperties as NSDictionary
|
|
|
|
guard let imagePixelWidth = imageProperties[kCGImagePropertyPixelWidth] as? NSNumber else {
|
|
continue
|
|
}
|
|
if imagePixelWidth.intValue > maxPixelSize * 2 || imagePixelWidth.intValue < maxPixelSize {
|
|
continue
|
|
}
|
|
|
|
guard let imagePixelHeight = imageProperties[kCGImagePropertyPixelHeight] as? NSNumber else {
|
|
continue
|
|
}
|
|
if imagePixelHeight.intValue > maxPixelSize * 2 || imagePixelHeight.intValue < maxPixelSize {
|
|
continue
|
|
}
|
|
|
|
return CGImageSourceCreateImageAtIndex(imageSource, i, nil)
|
|
}
|
|
|
|
|
|
// If the image data contains a smaller image than the max size, just return it.
|
|
for i in 0..<numberOfImages {
|
|
|
|
guard let cfImageProperties = CGImageSourceCopyPropertiesAtIndex(imageSource, i, nil) else {
|
|
continue
|
|
}
|
|
|
|
let imageProperties = cfImageProperties as NSDictionary
|
|
|
|
guard let imagePixelWidth = imageProperties[kCGImagePropertyPixelWidth] as? NSNumber else {
|
|
continue
|
|
}
|
|
if imagePixelWidth.intValue < 1 || imagePixelWidth.intValue > maxPixelSize {
|
|
continue
|
|
}
|
|
|
|
guard let imagePixelHeight = imageProperties[kCGImagePropertyPixelHeight] as? NSNumber else {
|
|
continue
|
|
}
|
|
if imagePixelHeight.intValue > 0 && imagePixelHeight.intValue <= maxPixelSize {
|
|
if let image = CGImageSourceCreateImageAtIndex(imageSource, i, nil) {
|
|
return image
|
|
}
|
|
}
|
|
}
|
|
|
|
return RSImage.createThumbnail(imageSource, maxPixelSize: maxPixelSize)
|
|
|
|
}
|
|
|
|
/// Create a thumbnail from a CGImageSource.
|
|
///
|
|
/// - Parameters:
|
|
/// - imageSource: The `CGImageSource` from which to create the thumbnail.
|
|
/// - maxPixelSize: The maximum dimension of the resulting image.
|
|
static func createThumbnail(_ imageSource: CGImageSource, maxPixelSize: Int) -> CGImage? {
|
|
let options = [kCGImageSourceCreateThumbnailWithTransform : true,
|
|
kCGImageSourceCreateThumbnailFromImageIfAbsent : true,
|
|
kCGImageSourceThumbnailMaxPixelSize : NSNumber(value: maxPixelSize)]
|
|
return CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary)
|
|
}
|
|
|
|
}
|