Search code examples
swiftnsdatacolor-profilecolor-managementcgcolorspace

Swift: Convert RGB (UIColor) to CMYK with ICC Profile


I've successfully made the following function to convert a UIColor into CMYK values using Swift 2 code in Xcode 7.2. However, the returned values are similar to a formula based conversion:

func RGBtoCMYK(rgbColor: UIColor) -> (c: CGFloat, m: CGFloat, y: CGFloat, k: CGFloat) {
    /*
    let iccFileName = "CoatedGRACoL2006"
    let iccProfile: CFDataRef = CGDataProviderCreateWithFilename(iccFileName) as! CFDataRef
    let colorSpace: CGColorSpaceRef = CGColorSpaceCreateWithICCProfile(iccProfile)!
    */
    let colorSpace: CGColorSpaceRef = CGColorSpaceCreateDeviceCMYK()!
    let intent = CGColorRenderingIntent.RenderingIntentPerceptual
    let cmykColor = CGColorCreateCopyByMatchingToColorSpace(colorSpace, intent, rgbColor.CGColor, nil)
    let c: CGFloat = round(CGColorGetComponents(cmykColor)[0] * 100)
    let m: CGFloat = round(CGColorGetComponents(cmykColor)[1] * 100)
    let y: CGFloat = round(CGColorGetComponents(cmykColor)[2] * 100)
    let k: CGFloat = round(CGColorGetComponents(cmykColor)[3] * 100)
    return (c, m, y, k)
}

Instead, I'd like to use an ICC Profile. In the remarked-out area I tried to change the ColorSpace from the CGCreateDeviceCMYK() to CGCreateWithICCProfile() which is looking for NSData input. I downloaded the standard CMYK ICC Profiles from Adobe and dragged "CoatedGRACoL2006.icc" into my Asset.xcassets project folder. The code compiled but generated a long list of errors while running the app. Regarding how to change the ColorSpace to an ICC Profile based color space, any input, comments, or suggestions would be appreciated.


Solution

  • You can use NSData to load the profile's data from your bundle. NSData and CFData are toll-free bridged so can use a NSData instance whenever an API requires aCFData one.

    I moved your color conversion method into an extension:

    extension UIColor {
        func colorComponentsByMatchingToColorSpace(colorSpace: CGColorSpace) -> (c: CGFloat, m: CGFloat, y: CGFloat, k: CGFloat) {
            let intent = CGColorRenderingIntent.RenderingIntentPerceptual
            let cmykColor = CGColorCreateCopyByMatchingToColorSpace(colorSpace, intent, self.CGColor, nil)
            let c: CGFloat = round(CGColorGetComponents(cmykColor)[0] * 100)
            let m: CGFloat = round(CGColorGetComponents(cmykColor)[1] * 100)
            let y: CGFloat = round(CGColorGetComponents(cmykColor)[2] * 100)
            let k: CGFloat = round(CGColorGetComponents(cmykColor)[3] * 100)
            return (c, m, y, k)
        }
    }
    

    To load the .icc file from your bundle and call the extension method you can use the following:

    guard let iccProfileURL = NSBundle.mainBundle().resourceURL?.URLByAppendingPathComponent("CoatedGRACoL2006.icc") else {
        return;
    }
    guard let iccProfileData = NSData(contentsOfURL: iccProfileURL) else {
        return;
    }
    guard let colorSpace = CGColorSpaceCreateWithICCProfile(iccProfileData) else {
        return;
    }
    let components = UIColor.redColor().colorComponentsByMatchingToColorSpace(colorSpace);
    print("\(components)")
    

    Note that the above loads the profile from your bundle resources and not from the asset catalog. (So you have to make sure that the .icc file is copied into your bundle during the "Copy Bundle Resources" build phase). If you want to use the asset catalog and you target iOS 9.0 or later, you can look into NSDataAsset.