I'm making an OS X app which creates a color scheme from the main colors of an image.
As a first step, I'm using NSCountedSet
and colorAtX
to get all the colors from an image and count their occurrences:
func sampleImage(#width: Int, height: Int, imageRep: NSBitmapImageRep) -> (NSCountedSet, NSCountedSet) {
// Store all colors from image
var colors = NSCountedSet(capacity: width * height)
// Store the colors from left edge of the image
var leftEdgeColors = NSCountedSet(capacity: height)
// Loop over the image pixels
var x = 0
var y = 0
while x < width {
while y < height {
// Instruments shows that `colorAtX` is very slow
// and using `NSCountedSet` is also very slow
if let color = imageRep.colorAtX(x, y: y) {
if x == 0 {
leftEdgeColors.addObject(color)
}
colors.addObject(color)
}
y++
}
// Reset y every x loop
y = 0
// We sample a vertical line every x pixels
x += 1
}
return (colors, leftEdgeColors)
}
My problem is that this is very slow. In Instruments, I see there's two big bottlenecks: with NSCountedSet
and with colorAtX
.
So first I thought maybe replace NSCountedSet
by a pure Swift equivalent, but the new implementation was unsurprisingly much slower than NSCountedSet
.
For colorAtX
, there's this interesting SO answer but I haven't been able to translate it to Swift (and I can't use a bridging header to Objective-C for this project).
My problem when trying to translate this is I don't understand the unsigned char
and char
parts in the answer.
What should I try to scan the colors faster than with colorAtX
?
Continue working on adapting the Objective-C answer because it's a good answer? Despite being stuck for now, maybe I can achieve this later.
Use another Foundation/Cocoa method that I don't know of?
Anything else that I could try to improve my code?
TL;DR
colorAtX
is slow, and I don't understand how to adapt this Objective-C answer to Swift because of unsigned char
.
The fastest alternative to colorAtX() would be iterating over the raw bytes of the image using
let bitmapBytes = imageRep.bitmapData
and composing the colour yourself from that information, which should be really simple if it's just RGBA data. Instead of your for x/y loop, do something like this...You will have to check the order of the RGBA values though, I just guessed!
The quickest way to maintain a count might just be a [Int, Int] dictionary of pixel values to counts, doing something like
colors[color]++
. Later on if you need to you can convert that to a NSColor usingNSColor(calibratedRed red: CGFloat, green green: CGFloat, blue blue: CGFloat, alpha alpha: CGFloat)