NetNewsWire/Shared/Extensions/IconImage.swift

136 lines
2.8 KiB
Swift

//
// IconImage.swift
// NetNewsWire
//
// Created by Maurice Parker on 11/5/19.
// Copyright © 2019 Ranchero Software. All rights reserved.
//
#if os(macOS)
import AppKit
#else
import UIKit
#endif
import RSCore
final class IconImage {
lazy var isDark: Bool = {
return image.isDark()
}()
lazy var isBright: Bool = {
return image.isBright()
}()
let image: RSImage
var isSymbol: Bool
init(_ image: RSImage, isSymbol: Bool = false) {
self.image = image
self.isSymbol = isSymbol
}
}
#if os(macOS)
extension NSImage {
func isDark() -> Bool {
return self.cgImage(forProposedRect: nil, context: nil, hints: nil)?.isDark() ?? false
}
func isBright() -> Bool {
return self.cgImage(forProposedRect: nil, context: nil, hints: nil)?.isBright() ?? false
}
}
#else
extension UIImage {
func isDark() -> Bool {
return self.cgImage?.isDark() ?? false
}
func isBright() -> Bool {
return self.cgImage?.isBright() ?? false
}
}
#endif
fileprivate enum ImageLuminanceType {
case regular, bright, dark
}
extension CGImage {
func isBright() -> Bool {
guard let imageData = self.dataProvider?.data, let luminanceType = getLuminanceType(from: imageData) else {
return false
}
return luminanceType == .bright
}
func isDark() -> Bool {
guard let imageData = self.dataProvider?.data, let luminanceType = getLuminanceType(from: imageData) else {
return false
}
return luminanceType == .dark
}
fileprivate func getLuminanceType(from data: CFData) -> ImageLuminanceType? {
guard let ptr = CFDataGetBytePtr(data) else {
return nil
}
let length = CFDataGetLength(data)
var pixelCount = 0
var totalLuminance = 0.0
for i in stride(from: 0, to: length, by: 4) {
let r = ptr[i]
let g = ptr[i + 1]
let b = ptr[i + 2]
let a = ptr[i + 3]
let luminance = (0.299 * Double(r) + 0.587 * Double(g) + 0.114 * Double(b))
if Double(a) > 0 {
totalLuminance += luminance
pixelCount += 1
}
}
let avgLuminance = totalLuminance / Double(pixelCount)
if totalLuminance == 0 || avgLuminance < 40 {
return .dark
} else if avgLuminance > 180 {
return .bright
} else {
return .regular
}
}
}
enum IconSize: Int, CaseIterable {
case small = 1
case medium = 2
case large = 3
private static let smallDimension = CGFloat(integerLiteral: 24)
private static let mediumDimension = CGFloat(integerLiteral: 36)
private static let largeDimension = CGFloat(integerLiteral: 48)
var size: CGSize {
switch self {
case .small:
return CGSize(width: IconSize.smallDimension, height: IconSize.smallDimension)
case .medium:
return CGSize(width: IconSize.mediumDimension, height: IconSize.mediumDimension)
case .large:
return CGSize(width: IconSize.largeDimension, height: IconSize.largeDimension)
}
}
}