22

Is there a Cocoa Touch way to convert colors from one color space to another?

At the end of this code:

UIColor *grey = [UIColor colorWithWhite: 0.5 alpha: 1.0];
CGColorRef greyRef = [grey CGColor];
int x = CGColorGetNumberOfComponents(greyRef);

...x is 2.

The reason I need this is I'm trying to copy colors to a list of color components for CGGradientCreateWithColorComponents, which needs all colors in a single colorspace. The problem is that grey is in the grayscale colorspace, rather than the RGB one. (The name escapes me at the moment, but it isn't important.)

CGGradientRef createGradient(NSInteger inCount, NSArray* inColors, CGFloat* inLocations) {
 CGColorSpaceRef theColorspace = CGColorSpaceCreateDeviceRGB( );
 size_t numberOfComponents = 4;
 NSInteger colorSize = numberOfComponents * sizeof( CGFloat );
 CGFloat *theComponents = malloc( inCount * colorSize );
 CGFloat *temp = theComponents;
 for ( NSInteger i = 0; i < inCount; i++ ) {
  UIColor *theColor = [inColors objectAtIndex: i];
  CGColorRef cgColor = [theColor CGColor];
  const CGFloat *components = CGColorGetComponents( cgColor );
  memmove( temp, components, colorSize );
  temp += numberOfComponents;
 }
 CGGradientRef gradient = CGGradientCreateWithColorComponents( theColorspace,
                                               theComponents, inLocations, inCount );
 CGColorSpaceRelease( theColorspace );
 free( theComponents );
 return gradient;
}

I know I can look for colors in the greyscale color space and convert them. But that only solves one case. From looking at this question, I think HSB is handled as well. But I'd like to write some code here and never think about it again, which means supporting not just the color spaces that are there now but anything Apple could conceivably add in the future. Am I out of luck?

Community
  • 1
  • 1
Steven Fisher
  • 44,462
  • 20
  • 138
  • 192

5 Answers5

8

I'm not sure how to automatically convert them, but to identify different color spaces you can get the CGColorSpaceModel from the color's CGColorSpaceRef:

UIColor* color = some color;
CGColorSpaceRef colorSpace = CGColorGetColorSpace([color CGColor]);
CGColorSpaceModel colorSpaceModel = CGColorSpaceGetModel(colorSpace);

Then you can compare colorSpaceModel with the constants defined in CoreGraphics/CGColorSpace.h. UIColor's getRed:green:blue:alpha works for kCGColorSpaceModelRGB, whereas getWhite:alpha works for kCGColorSpaceModelMonochrome.

Note that a UIColor that was created with colorWithHue:saturation:brightness:alpha: will actually be in the RGB color space.

damian
  • 3,604
  • 1
  • 27
  • 46
  • This is a pretty good answer, though you might want to mention that HSB colors are put into the RGB color space. Thus, it's perfectly valid to call `getRed:green:blue:alpha:` on them. – Steven Fisher Jun 07 '13 at 21:18
  • Hmm, there doesn't seem to be a `kCGColorSpaceModelHSB` :/ – damian Jun 23 '15 at 08:58
  • Nope. They get mapped to the RGB space; use the RGB method for them. From the docs for `colorWithHue:saturation:brightness:alpha:` **The color information represented by [the returned] object is in the device RGB colorspace.** – Steven Fisher Jun 24 '15 at 22:56
  • I've added the note you suggested, but I feel a bit like it's unnecessary noise in the answer.. – damian Jun 25 '15 at 09:57
4

In swift 3 we can directly use

 let colorSpace = uiColor.cgColor.colorSpace
 let csModel = colorSpace.model

to get the color space and color space model from a UIColor.

CodeBrew
  • 6,457
  • 2
  • 43
  • 48
2

For colors created using [UIColor colorWithRed:green:blue:alpha:], you can use UIColor getRed:green:blue:alpha:, described in UIColor Class Reference. It's part of iOS 5 and later.

If the color was created with colorWithWhite:alpha: you can use the getWhite:alpha: instead, described in UIColor Class Reference.

To determine which color space is being used, you can use CGColorGetColorSpace([color colorSpace]). But it's probably easier to just check the result of the method call, then fail over to the next attempt. Something like this:

if ([color getRed:&red green:&green blue:&blue alpha:&alpha]) {
    // red, green and blue are all valid
} else if if ([color getWhite:&white alpha:&alpha]) {
    red = white; green = white; blue = white;
} else {
    // can't get it
}

I don't know how to handle color constants such as [UIColor lightGrayColor], other than drawing them to a temporary bitmap and detecting them. Some of these color constants are actually textures; your best bet is probably to avoid them.

If you plan on doing this a lot, it's an appropriate use of a category:

@interface UIColor(sf)
- (BOOL)sfGetRed:(CGFloat *)red green:(CGFloat *)green blue:(CGFloat *)blue alpha:(CGFloat *)alpha;
@end

@implementation UIColor(sf)
- (BOOL)sfGetRed:(CGFloat *)red green:(CGFloat *)green blue:(CGFloat *)blue alpha:(CGFloat *)alpha {
    if ([self getRed:red green:green blue:blue alpha:alpha]) return YES;
    CGFloat white;
    if ([self getWhite:&white alpha:alpha]) {
        if (red) *red = white;
        if (green) *green = white;
        if (blue) *blue = white;
        return YES;
    }
    return NO;
}
@end
Steven Fisher
  • 44,462
  • 20
  • 138
  • 192
  • The question specifically asks how to convert colours between colour spaces; if you call this method it may return `NO` meaning that the receiver `UIColor` is in a different colour space to the current colour space. For example, `BOOL success = [[UIColor lightGrayColor] getRed:&dummy green:&dummy blue:&dummy]` returns `NO`. – damian Jun 07 '13 at 10:04
  • It requires a compatible color space, not the same color space. Some colors are simply not convertible. It will worth with `colorWithWhite:alpha:`. You don't appear to have read the full question, and you've downvoted me posting the correct answer. – Steven Fisher Jun 07 '13 at 15:21
  • I'm not sure what 'compatible' color space means, in that case. Surely a monochrome color space is 'compatible' with an RGB color space? I've downvoted your answer because, in the case of a monochrome color, the method you mention not only doesn't work, but it subtly doesn't work and it's not clear why. – damian Jun 07 '13 at 18:01
  • I think I will test this all again and update my answer with what I find. Thanks for alerting me this might be wrong. – Steven Fisher Jun 07 '13 at 21:08
  • It looks like you're correct. I've updated the answer to reflect that the grayscale color space is not compatible with RGB. Weird. – Steven Fisher Jun 07 '13 at 21:22
2

Use the CGColor method:

func converted(to _: CGColorSpace, intent: CGColorRenderingIntent, options: CFDictionary?) -> CGColor?

I needed it to make sure all the CGColor's were in the CGColorSpaceCreateDeviceRGB color space .

cgColor.converted(to: CGColorSpaceCreateDeviceRGB(), intent: .defaultIntent, options: nil)

https://developer.apple.com/documentation/coregraphics/cgcolor/1455493-converted

enter image description here

SirRupertIII
  • 12,324
  • 20
  • 72
  • 121
1

Another way to convert these is by drawing them into contexts and letting core graphics do the conversion for you. See my answer to this question:

Get RGB value from UIColor presets

Community
  • 1
  • 1
Jesse Rusak
  • 56,530
  • 12
  • 101
  • 102
  • 2
    Wow, really? That seems like a terrible idea. – damian Jun 07 '13 at 10:34
  • @damian No question it's ugly, but so far as I know it's the only way that reliably works regardless of how the original color was created. Given that the original question was worried about "supporting not just the color spaces that are there now but anything Apple could conceivably add in the future" I don't really see a good alternative. – Jesse Rusak Jun 07 '13 at 23:51