Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proper way to convert CGColors between colorspaces

There are several questions out there that ask about converting colors between colorspaces on Apple platforms. Unfortunately, the answers quite often involve NSColor or UIColor -- non portable Objective-C classes that cannot be interchangeably used on OS X and iOS.

So I'd like to ask a very specific thing that I'm sure there must be a good answer to out there. I simply cannot believe that Apple would not foresee the need for this.

How does one convert CGColor from one colorspace (for example, monochrome) to another one (for example, RGB) in a generic way, supporting all CGColorSpace types, while using solely the portable Core Graphics functions?


Some context. I need to multiply the value provided by an online service with a value stored in UIColor. Correct way to extract the RGB components prior to iOS5, which finally introduced the method -[UIColor getRed:green:blue:alpha:], is to use CGColorGetComponents(). I then multiply this color with the color fetched from the online service. This fails in case +[UIColor grayColor] was used to generate the UIColor. Meaning, I need to convert the color from the greyscale colorspace into RGB. In this case, it's easy. What about some other colorspace being provided? Or what if in a theoretical future scenario I just want to process a single pixel's color?

There is a suggestion somewhere that I paint a pixel into a bitmap context and then read this pixel. That's insane, and I hope it isn't the only way to do this. Obviously the drawing method can figure out how to perform the conversion; how can we leverage this without creating a bitmap context solely to draw a pixel in it?


Additional research:

  • This article on color conversion, aside from UIColor's undocumented/private method -styleString, more interestingly mentions also undocumented CGColorTransforms.
  • ColorCG.cpp from WebKit suggests there is a header named CoreGraphics/CGColorTransform.h. Unfortunately, this header does not exist, at least not on Mountain Lion. Why would Apple hide these APIs?
  • The only other sensible resource I found mentioning CGColorTransforms is FreeQuartz, a free/open source reimplementation of Core Graphics.

Filed a radar with Apple, #12141580, to open up and document CGColorTransform. I'm not holding my breath, though, so if there are other sensible suggestions, I'm all ears.

like image 786
Ivan Vučica Avatar asked Aug 21 '12 09:08

Ivan Vučica


3 Answers

Apple added this API in iOS 9 and macOS 10.11:

  • ObjC: CGColorCreateCopyByMatchingToColorSpace
  • Swift: CGColor.converted(to:intent:options:)
like image 82
an0 Avatar answered Oct 14 '22 14:10

an0


Apple's official response to rdar #12141580 where I requested exposing CGColorTransform:

Thanks very much for your feedback.

Engineering has determined that the behavior will not be changed based on the following information:

Use ColorSync.

If you have questions regarding the resolution of this issue, please update your bug report with that information.

We are now closing this bug report.

Please be sure to regularly check new Apple releases for any updates that might affect this issue.

Whoever wrote that response obviously felt compelled to explain the decision in great detail. Especially a decision that recommends the use of a highly complex API such as ColorSync.

like image 35
Ivan Vučica Avatar answered Oct 14 '22 15:10

Ivan Vučica


The question explicitly says they want to avoid drawing on a bitmap context created solely for the purpose of converting colors, but i didn't realize and came up with this.

I'll leave this as reference for anyone which could stumble on this question and want to know how you would do the transformation by drawing on a temporary bitmap

CGColorRef source;
CGColorSpaceRef targetColorSpace;
int components = CGColorSpaceGetNumberOfComponents(targetColorSpace);
CGFloat temp-buffer[components];
CGContextRef temp-bmp = CGBitmapContextCreate(
    temp-buffer,
    1,1,8 * sizeof(CGFloat),components,
    targetColorSpace,
    kCGBitmapFloatComponents );
CGContextSetFillColorWithColor(temp-bmp,source);
CGContextFillRect(temp-bmp,CGRectMake(0,0,1,1));
CGColor target = CGColorCreate(targetColorSpace,temp-buffer);

/* then you cleanup and destroy created objects */

I didn't try this code, it could need some fixes.

like image 4
pqnet Avatar answered Oct 14 '22 14:10

pqnet