diff --git a/Package.swift b/Package.swift index e463de1..c4f6735 100644 --- a/Package.swift +++ b/Package.swift @@ -18,6 +18,7 @@ let package = Package( .target(name: "CyanConcurrency"), .target(name: "CyanSwiftUI", dependencies: ["CyanExtensions"]), .target(name: "CyanUI", dependencies: ["CyanSwiftUI"]), + .target(name: "CyanImage", dependencies: ["CyanExtensions"]), .target( name: "CyanKit", dependencies: [ @@ -27,6 +28,7 @@ let package = Package( "CyanConcurrency", "CyanSwiftUI", "CyanUI", + "CyanImage", ]), .testTarget( name: "CyanConcurrencyTests", diff --git a/Sources/CyanImage/Color.swift b/Sources/CyanImage/Color.swift new file mode 100644 index 0000000..ae84e94 --- /dev/null +++ b/Sources/CyanImage/Color.swift @@ -0,0 +1,41 @@ +// +// Created by ktiays on 2022/10/17. +// Copyright (c) 2022 ktiays. All rights reserved. +// + +#if canImport(UIKit) + +import UIKit + +extension UIImage { + + public convenience init?(with color: UIColor, size: CGSize = .init(width: 1, height: 1)) { + UIGraphicsBeginImageContextWithOptions(size, false, 0) + color.setFill() + UIRectFill(CGRect(origin: CGPoint.zero, size: size)) + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + guard let cgImage = image?.cgImage else { + return nil + } + self.init(cgImage: cgImage) + } + +} + +#elseif canImport(AppKit) + +import AppKit + +extension NSImage { + + public convenience init(color: NSColor, size: NSSize = .init(width: 1, height: 1)) { + self.init(size: size) + lockFocus() + color.drawSwatch(in: NSRect(origin: .zero, size: size)) + unlockFocus() + } + +} + +#endif diff --git a/Sources/CyanImage/DominantColors.swift b/Sources/CyanImage/DominantColors.swift new file mode 100644 index 0000000..57c19c0 --- /dev/null +++ b/Sources/CyanImage/DominantColors.swift @@ -0,0 +1,73 @@ +// +// Created by ktiays on 2022/10/11. +// Copyright (c) 2022 ktiays. All rights reserved. +// + +import CoreImage +import CyanExtensions + +extension CIImage { + + public func dominantColor(clusterCount: Int = 5) -> [PlatformColor] { + guard let kMeansFilter = CIFilter(name: "CIKMeans") else { + return [] + } + + kMeansFilter.setValue(self, forKey: kCIInputImageKey) + kMeansFilter.setValue(CIVector(cgRect: self.extent), forKey: kCIInputExtentKey) + kMeansFilter.setValue(clusterCount, forKey: "inputCount") + kMeansFilter.setValue(20, forKey: "inputPasses") + + guard var outputImage = kMeansFilter.outputImage else { + return [] + } + + outputImage = outputImage.settingAlphaOne(in: outputImage.extent) + + let context = CIContext() + var bitmap = [UInt8](repeating: 0, count: 4 * clusterCount) + context.render(outputImage, + toBitmap: &bitmap, + rowBytes: 4 * clusterCount, + bounds: outputImage.extent, + format: CIFormat.RGBA8, + colorSpace: self.colorSpace!) + + var dominantColors = [PlatformColor]() + + for i in 0.. [UIColor] { + CIImage(image: self)?.dominantColor(clusterCount: clusterCount) ?? [] + } + +} + +#elseif canImport(AppKit) + +import AppKit + +extension NSImage { + + public func dominantColors(clusterCount: Int = 5) -> [NSColor] { + ciImage?.dominantColor(clusterCount: clusterCount) ?? [] + } + +} + +#endif diff --git a/Sources/CyanImage/NSImage+CIImage.swift b/Sources/CyanImage/NSImage+CIImage.swift new file mode 100644 index 0000000..bddeb8a --- /dev/null +++ b/Sources/CyanImage/NSImage+CIImage.swift @@ -0,0 +1,32 @@ +// +// Created by ktiays on 2022/10/14. +// Copyright (c) 2022 ktiays. All rights reserved. +// + +#if canImport(AppKit) + +import AppKit + +extension NSImage { + + public var ciImage: CIImage? { + guard let data = self.tiffRepresentation, + let bitmap = NSBitmapImageRep(data: data) else { + return nil + } + return CIImage(bitmapImageRep: bitmap) + } + +} + +extension NSImage { + + public convenience init(ciImage: CIImage) { + let imageRep = NSCIImageRep(ciImage: ciImage) + self.init(size: imageRep.size) + self.addRepresentation(imageRep) + } + +} + +#endif