Skip to content

Commit

Permalink
Swift Concurrency, Unit Tests, Update Demo
Browse files Browse the repository at this point in the history
  • Loading branch information
Sam-Spencer committed Nov 9, 2022
1 parent fd5e726 commit 0a6aeaf
Show file tree
Hide file tree
Showing 33 changed files with 1,106 additions and 702 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>classNames</key>
<dict>
<key>DominantColorTests</key>
<dict>
<key>testPerformanceExample()</key>
<dict>
<key>com.apple.XCTPerformanceMetric_WallClockTime</key>
<dict>
<key>baselineAverage</key>
<real>0.033600</real>
<key>baselineIntegrationDisplayName</key>
<string>Local Baseline</string>
</dict>
</dict>
</dict>
</dict>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>runDestinationsByUUID</key>
<dict>
<key>B8FEC642-12BE-4F3F-8764-91F1D5AB1965</key>
<dict>
<key>localComputer</key>
<dict>
<key>busSpeedInMHz</key>
<integer>400</integer>
<key>cpuCount</key>
<integer>1</integer>
<key>cpuKind</key>
<string>6-Core Intel Core i9</string>
<key>cpuSpeedInMHz</key>
<integer>2900</integer>
<key>logicalCPUCoresPerPackage</key>
<integer>12</integer>
<key>modelCode</key>
<string>MacBookPro15,3</string>
<key>physicalCPUCoresPerPackage</key>
<integer>6</integer>
<key>platformIdentifier</key>
<string>com.apple.platform.macosx</string>
</dict>
<key>targetArchitecture</key>
<string>x86_64</string>
<key>targetDevice</key>
<dict>
<key>modelCode</key>
<string>iPhone15,2</string>
<key>platformIdentifier</key>
<string>com.apple.platform.iphonesimulator</string>
</dict>
</dict>
</dict>
</dict>
</plist>
17 changes: 0 additions & 17 deletions DominantColor.podspec

This file was deleted.

169 changes: 132 additions & 37 deletions DominantColor.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1170"
LastUpgradeVersion = "1410"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1170"
LastUpgradeVersion = "1410"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1170"
LastUpgradeVersion = "1410"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "1170"
LastUpgradeVersion = "1410"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
2 changes: 1 addition & 1 deletion DominantColor/Mac/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2014 Indragie Karunaratne. All rights reserved.</string>
<string>Copyright © 2022 Sam Spencer. All rights reserved.</string>
<key>NSMainNibFile</key>
<string>MainMenu</string>
<key>NSPrincipalClass</key>
Expand Down
100 changes: 100 additions & 0 deletions DominantColor/Shared/CGImage+DominantColors.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
//
// CGImage+DominantColors.swift
// DominantColor
//
// Created by Sam Spencer on 11/9/22.
// Copyright © 2022 Indragie Karunaratne. All rights reserved.
//

import CoreGraphics
import Foundation
import simd

internal extension CGImage {

/// Computes the dominant colors in an image
///
/// - parameter maxSampledPixels: Maximum number of pixels to sample in the image.
/// If the total number of pixels in the image exceeds this value, it will be
/// downsampled to meet the constraint.
/// - parameter accuracy: Level of accuracy to use when grouping similar colors.
/// Higher accuracy will come with a performance tradeoff.
/// - parameter seed: Seed to use when choosing the initial points for grouping of
/// similar colors. The same seed is guaranteed to return the same colors every
/// time.
/// - parameter memoizeConversions: Whether to memoize conversions from RGB to the
/// LAB color space (used for grouping similar colors). Memoization will only yield
/// better performance for large values of `maxSampledPixels` in images that are
/// primarily comprised of flat colors. If this information about the image is not
/// known beforehand, it is best to not memoize.
///
/// - returns: A list of dominant colors in the image sorted from most dominant to
/// least dominant.
///
func dominantColorsInImage(
maxSampledPixels: Int,
accuracy: GroupingAccuracy,
seed: UInt64,
memoizeConversions: Bool
) async -> [CGColor] {
let calculator = DominantColorCalculator()
let rgbaContext = RGBAContext()
let colorSpaceConverter = ColorSpaceConverter()
let kMeansProcessor = KMeans()

let (scaledWidth, scaledHeight) = await calculator.scaledDimensionsForPixelLimit(
maxSampledPixels,
width: width,
height: height
)

// Downsample the image if necessary, so that the total number of pixels sampled
// does not exceed the specified maximum.
//
let context = await rgbaContext.createRGBAContext(scaledWidth, height: scaledHeight)
context.draw(self, in: CGRect(x: 0, y: 0, width: Int(scaledWidth), height: Int(scaledHeight)))

// Get the RGB colors from the bitmap context, ignoring any pixels that have alpha
// transparency. Also convert the colors to the LAB color space.
//
var labValues = [simd_float3]()
labValues.reserveCapacity(Int(scaledWidth * scaledHeight))

let RGBToLAB: (RGBAPixel) async -> simd_float3 = await {
let f: (RGBAPixel) async -> simd_float3 = {
await colorSpaceConverter.IN_RGBToLAB($0.toRGBVector())
}
if memoizeConversions == true {
return await memoize(f)
} else {
return f
}
}()

await rgbaContext.enumerateRGBAContext(context) { _, _, pixel in
if pixel.a == UInt8.max {
async let rgbLab = RGBToLAB(pixel)
labValues.append(await rgbLab)
}
}

// Cluster the colors using the k-means algorithm.
//
let k = 16 // Magic number for now instead of: async let k = calculator.selectKForElements(labValues)
async let distance = calculator.distanceForAccuracy(accuracy)
var clusters = await kMeansProcessor.kmeans(labValues, k: k, seed: seed, distance: await distance)

// Sort the clusters by size in descending order so that the most dominant colors
// come first.
//
clusters.sort { $0.size > $1.size }

let colorClusters = await clusters.asyncMap { cluster in
async let rgbColor = colorSpaceConverter.IN_LABToRGB(cluster.centroid)
return await rgbColor.fromRGBVectorToCGColor()
}

return colorClusters
}

}
33 changes: 33 additions & 0 deletions DominantColor/Shared/ClusteredTypeProtocol.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
//
// ClusteredTypeProtocol.swift
// DominantColor
//
// Created by Sam Spencer on 11/9/22.
// Copyright © 2022 Indragie Karunaratne. All rights reserved.
//

import Foundation

/// Represents a type that can be clustered using the k-means clustering
/// algorithm.
///
protocol ClusteredType {

/// Used to compute average values to determine the cluster centroids.
///
static func +(lhs: Self, rhs: Self) -> Self

/// Used to compute average values to determine the cluster centroids.
///
static func /(lhs: Self, rhs: Int) -> Self

/// Identity value such that `x + identity = x`. Typically the `0` vector.
///
static var identity: Self { get }

}

struct Cluster<T : ClusteredType> {
let centroid: T
let size: Int
}
Loading

0 comments on commit 0a6aeaf

Please sign in to comment.