Search code examples
macoscolorsreverse-engineering

How does the Apple Color List format (CLR) work?


I am trying to parse the color data from a .clr file, which is used on mac to store system wide color palettes. I created a test file using Affinity Designer, which is then stored in ~/Library/Colors. I tried playing around with different colors to then get a clue how the colorvalues are stored in the file, but whatever color I tried, I always get really strange results which seem to be almost completely unrelated to the color in question.

For example, I saved a color called "Test 2" which is just a complete black (#000000). The file then contains the name of the color as a UTF8-String, so I can see where the color is stored.

Before the name of the first color there is a longer string which only appears once at the beginning of the file, no matter how many colors it contains, so I guess this should be the header.

After the name of the color, there are a few bytes of binary data before the name of the second color, so I assumed that has to be the color data. But what is really strange, is that said block of data contains

����������;����;���@<����

or

fffd fffd fffd fffd fffd 1 fffd fffd fffd fffd fffd 3b fffd fffd fffd fffd 3b fffd fffd fffd 40 3c 1 fffd fffd fffd fffd 6

in a hexadecimal format, just for the color black. The second color however, which equals to the color #010101, has the binary data

��������

or

fffd fffd fffd fffd fffd 1 fffd fffd fffd

I fail to make any sense of how this file format stores the color data, extensive research also didn't bring any helpful results, which fit my problem.


Solution

  • As @TomDoodler commented on the question, the .clr file is the serialization of the NSColorList class.

    I wrote a MacOS playground that opens the .clr file and prints out the color names and RGB values. I wanted to create camelCase names for the colors, so that's why I added a String extension.

    For Swift 4.

    import Cocoa
    
    extension String {
        func lowercasedFirstLetter() -> String {
            return prefix(1).lowercased() + dropFirst()
        }
    
        mutating func lowercaseFirstLetter() {
            self = self.lowercasedFirstLetter()
        }
    }
    
    func floatToInt(_ f : CGFloat) -> Int {
        return Int(f * 255)
    }
    
    let colorList = NSColorList.init(name: "clr", fromFile: "/Path/To/Yourfile.clr")
    if let allKeys = colorList?.allKeys {
        for key in allKeys {
            if let color = colorList?.color(withKey: key) {
                let red = floatToInt(color.redComponent)
                let green = floatToInt(color.greenComponent)
                let blue = floatToInt(color.blueComponent)
                let hexString = NSString(format: "#%02X%02X%02X", red, green, blue)
                let name = key.description.replacingOccurrences(of: " ", with: "").lowercasedFirstLetter()
                print(name, red, green, blue, hexString)
                // print("static let \(name) = UIColor.init(\"\(hexString)\")")
           }
         }
    }