mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-09-07 16:28:38 +09:00
278 lines
10 KiB
Swift
Vendored
278 lines
10 KiB
Swift
Vendored
//
|
|
// UIImageColors.swift
|
|
// https://github.com/jathu/UIImageColors
|
|
//
|
|
// Created by Jathu Satkunarajah (@jathu) on 2015-06-11 - Toronto
|
|
// Original Cocoa version by Panic Inc. - Portland
|
|
//
|
|
|
|
import UIKit
|
|
|
|
public struct UIImageColors {
|
|
public var background: UIColor!
|
|
public var primary: UIColor!
|
|
public var secondary: UIColor!
|
|
public var detail: UIColor!
|
|
}
|
|
|
|
class PCCountedColor {
|
|
let color: UIColor
|
|
let count: Int
|
|
|
|
init(color: UIColor, count: Int) {
|
|
self.color = color
|
|
self.count = count
|
|
}
|
|
}
|
|
|
|
extension CGColor {
|
|
var components: [CGFloat] {
|
|
get {
|
|
var red = CGFloat()
|
|
var green = CGFloat()
|
|
var blue = CGFloat()
|
|
var alpha = CGFloat()
|
|
UIColor(cgColor: self).getRed(&red, green: &green, blue: &blue, alpha: &alpha)
|
|
return [red,green,blue,alpha]
|
|
}
|
|
}
|
|
}
|
|
|
|
extension UIColor {
|
|
|
|
var isDarkColor: Bool {
|
|
let RGB = self.cgColor.components
|
|
return (0.2126 * RGB[0] + 0.7152 * RGB[1] + 0.0722 * RGB[2]) < 0.5
|
|
}
|
|
|
|
var isBlackOrWhite: Bool {
|
|
let RGB = self.cgColor.components
|
|
return (RGB[0] > 0.91 && RGB[1] > 0.91 && RGB[2] > 0.91) || (RGB[0] < 0.09 && RGB[1] < 0.09 && RGB[2] < 0.09)
|
|
}
|
|
|
|
func isDistinct(compareColor: UIColor) -> Bool {
|
|
let bg = self.cgColor.components
|
|
let fg = compareColor.cgColor.components
|
|
let threshold: CGFloat = 0.25
|
|
|
|
if fabs(bg[0] - fg[0]) > threshold || fabs(bg[1] - fg[1]) > threshold || fabs(bg[2] - fg[2]) > threshold {
|
|
if fabs(bg[0] - bg[1]) < 0.03 && fabs(bg[0] - bg[2]) < 0.03 {
|
|
if fabs(fg[0] - fg[1]) < 0.03 && fabs(fg[0] - fg[2]) < 0.03 {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func colorWithMinimumSaturation(minSaturation: CGFloat) -> UIColor {
|
|
var hue: CGFloat = 0.0
|
|
var saturation: CGFloat = 0.0
|
|
var brightness: CGFloat = 0.0
|
|
var alpha: CGFloat = 0.0
|
|
self.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha)
|
|
|
|
if saturation < minSaturation {
|
|
return UIColor(hue: hue, saturation: minSaturation, brightness: brightness, alpha: alpha)
|
|
} else {
|
|
return self
|
|
}
|
|
}
|
|
|
|
func isContrastingColor(compareColor: UIColor) -> Bool {
|
|
let bg = self.cgColor.components
|
|
let fg = compareColor.cgColor.components
|
|
|
|
let bgLum = 0.2126 * bg[0] + 0.7152 * bg[1] + 0.0722 * bg[2]
|
|
let fgLum = 0.2126 * fg[0] + 0.7152 * fg[1] + 0.0722 * fg[2]
|
|
|
|
let bgGreater = bgLum > fgLum
|
|
let nom = bgGreater ? bgLum : fgLum
|
|
let denom = bgGreater ? fgLum : bgLum
|
|
let contrast = (nom + 0.05) / (denom + 0.05)
|
|
return 1.6 < contrast
|
|
}
|
|
|
|
}
|
|
|
|
extension UIImage {
|
|
private func resizeForUIImageColors(newSize: CGSize) -> UIImage {
|
|
UIGraphicsBeginImageContextWithOptions(newSize, false, 0)
|
|
defer {
|
|
UIGraphicsEndImageContext()
|
|
}
|
|
self.draw(in: CGRect(x: 0, y: 0, width: newSize.width, height: newSize.height))
|
|
guard let result = UIGraphicsGetImageFromCurrentImageContext() else {
|
|
fatalError("UIImageColors.resizeForUIImageColors failed: UIGraphicsGetImageFromCurrentImageContext returned nil")
|
|
}
|
|
|
|
return result
|
|
}
|
|
|
|
/**
|
|
Get `UIImageColors` from the image asynchronously (in background thread).
|
|
Discussion: Use smaller sizes for better performance at the cost of quality colors. Use larger sizes for better color sampling and quality at the cost of performance.
|
|
|
|
- parameter scaleDownSize: Downscale size of image for sampling, if `CGSize.zero` is provided, the sample image is rescaled to a width of 250px and the aspect ratio height.
|
|
- parameter completionHandler: `UIImageColors` for this image.
|
|
*/
|
|
public func getColors(scaleDownSize: CGSize = CGSize.zero, completionHandler: @escaping (UIImageColors) -> Void) {
|
|
DispatchQueue.global().async {
|
|
let result = self.getColors(scaleDownSize: scaleDownSize)
|
|
|
|
DispatchQueue.main.async {
|
|
completionHandler(result)
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
Get `UIImageColors` from the image synchronously (in main thread).
|
|
Discussion: Use smaller sizes for better performance at the cost of quality colors. Use larger sizes for better color sampling and quality at the cost of performance.
|
|
|
|
- parameter scaleDownSize: Downscale size of image for sampling, if `CGSize.zero` is provided, the sample image is rescaled to a width of 250px and the aspect ratio height.
|
|
|
|
- returns: `UIImageColors` for this image.
|
|
*/
|
|
public func getColors(scaleDownSize: CGSize = CGSize.zero) -> UIImageColors {
|
|
|
|
var scaleDownSize = scaleDownSize
|
|
|
|
if scaleDownSize == CGSize.zero {
|
|
let ratio = self.size.width/self.size.height
|
|
let r_width: CGFloat = 250
|
|
scaleDownSize = CGSize(width: r_width, height: r_width/ratio)
|
|
}
|
|
|
|
var result = UIImageColors()
|
|
|
|
let cgImage = self.resizeForUIImageColors(newSize: scaleDownSize).cgImage!
|
|
let width: Int = cgImage.width
|
|
let height: Int = cgImage.height
|
|
|
|
let blackColor = UIColor(red: 0, green: 0, blue: 0, alpha: 1)
|
|
let whiteColor = UIColor(red: 1, green: 1, blue: 1, alpha: 1)
|
|
|
|
let randomColorsThreshold = Int(CGFloat(height)*0.01)
|
|
let sortedColorComparator: Comparator = { (main, other) -> ComparisonResult in
|
|
let m = main as! PCCountedColor, o = other as! PCCountedColor
|
|
if m.count < o.count {
|
|
return ComparisonResult.orderedDescending
|
|
} else if m.count == o.count {
|
|
return ComparisonResult.orderedSame
|
|
} else {
|
|
return ComparisonResult.orderedAscending
|
|
}
|
|
}
|
|
|
|
guard let data = CFDataGetBytePtr(cgImage.dataProvider!.data) else {
|
|
fatalError("UIImageColors.getColors failed: could not get cgImage data")
|
|
}
|
|
|
|
// Filter out and collect pixels from image
|
|
let imageColors = NSCountedSet(capacity: width * height)
|
|
|
|
for x in 0..<width {
|
|
for y in 0..<height {
|
|
let pixel: Int = ((width * y) + x) * 4
|
|
// Only consider pixels with 50% opacity or higher
|
|
if 127 <= data[pixel+3] {
|
|
imageColors.add(UIColor(
|
|
red: CGFloat(data[pixel+2])/255,
|
|
green: CGFloat(data[pixel+1])/255,
|
|
blue: CGFloat(data[pixel])/255,
|
|
alpha: 1.0
|
|
))
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get background color
|
|
var enumerator = imageColors.objectEnumerator()
|
|
var sortedColors = NSMutableArray(capacity: imageColors.count)
|
|
while let kolor = enumerator.nextObject() as? UIColor {
|
|
let colorCount = imageColors.count(for: kolor)
|
|
if randomColorsThreshold < colorCount {
|
|
sortedColors.add(PCCountedColor(color: kolor, count: colorCount))
|
|
}
|
|
}
|
|
sortedColors.sort(comparator: sortedColorComparator)
|
|
|
|
var proposedEdgeColor: PCCountedColor
|
|
if 0 < sortedColors.count {
|
|
proposedEdgeColor = sortedColors.object(at: 0) as! PCCountedColor
|
|
} else {
|
|
proposedEdgeColor = PCCountedColor(color: blackColor, count: 1)
|
|
}
|
|
|
|
if proposedEdgeColor.color.isBlackOrWhite && 0 < sortedColors.count {
|
|
for i in 1..<sortedColors.count {
|
|
let nextProposedEdgeColor = sortedColors.object(at: i) as! PCCountedColor
|
|
if (CGFloat(nextProposedEdgeColor.count)/CGFloat(proposedEdgeColor.count)) > 0.3 {
|
|
if !nextProposedEdgeColor.color.isBlackOrWhite {
|
|
proposedEdgeColor = nextProposedEdgeColor
|
|
break
|
|
}
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
result.background = proposedEdgeColor.color
|
|
|
|
// Get foreground colors
|
|
enumerator = imageColors.objectEnumerator()
|
|
sortedColors.removeAllObjects()
|
|
sortedColors = NSMutableArray(capacity: imageColors.count)
|
|
let findDarkTextColor = !result.background.isDarkColor
|
|
|
|
while var kolor = enumerator.nextObject() as? UIColor {
|
|
kolor = kolor.colorWithMinimumSaturation(minSaturation: 0.15)
|
|
if kolor.isDarkColor == findDarkTextColor {
|
|
let colorCount = imageColors.count(for: kolor)
|
|
sortedColors.add(PCCountedColor(color: kolor, count: colorCount))
|
|
}
|
|
}
|
|
sortedColors.sort(comparator: sortedColorComparator)
|
|
|
|
for curContainer in sortedColors {
|
|
let kolor = (curContainer as! PCCountedColor).color
|
|
|
|
if result.primary == nil {
|
|
if kolor.isContrastingColor(compareColor: result.background) {
|
|
result.primary = kolor
|
|
}
|
|
} else if result.secondary == nil {
|
|
if !result.primary.isDistinct(compareColor: kolor) || !kolor.isContrastingColor(compareColor: result.background) {
|
|
continue
|
|
}
|
|
|
|
result.secondary = kolor
|
|
} else if result.detail == nil {
|
|
if !result.secondary.isDistinct(compareColor: kolor) || !result.primary.isDistinct(compareColor: kolor) || !kolor.isContrastingColor(compareColor: result.background) {
|
|
continue
|
|
}
|
|
|
|
result.detail = kolor
|
|
break
|
|
}
|
|
}
|
|
|
|
let isDarkBackgound = result.background.isDarkColor
|
|
|
|
if result.primary == nil {
|
|
result.primary = isDarkBackgound ? whiteColor:blackColor
|
|
}
|
|
|
|
if result.secondary == nil {
|
|
result.secondary = isDarkBackgound ? whiteColor:blackColor
|
|
}
|
|
|
|
if result.detail == nil {
|
|
result.detail = isDarkBackgound ? whiteColor:blackColor
|
|
}
|
|
|
|
return result
|
|
}
|
|
}
|