PDA

View Full Version : CG, Contexts, Colors, Insanity




KnightWRX
Feb 22, 2012, 10:22 PM
Now, I'm not quite so familiar with Quartz 2D and don't quite get this, if someone can explain it, it would be appreciated.

The following code didn't work, except to display white text :

UIFont * usedfont = ...
CGFloat color4f [] = ...

CGRect currentimage = ...;
CGColorSpaceRef colorSpace;
CGContextRef context;

colorSpace = CGColorSpaceCreateDeviceRGB();
context = CGBitmapContextCreate(imagePixels,
width,
height,
8,
width * bytesPerChannel,
colorSpace,
kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);

CGContextTranslateCTM(context, 0.0f, height);
CGContextScaleCTM(context, 1.0f, -1.0f);
CGContextSetFillColor(context, color4f);

UIGraphicsPushContext(context);
[string drawInRect: currentimage withFont: usedfont lineBreakMode: UILineBreakModeWordWrap];
UIGraphicsPopContext();

CGContextRelease(context);
CGColorSpaceRelease(colorSpace);


Now, the bolded line, I simply changed it to the following, no other code changed in the function (I deleted a bunch of initialization here for readability) and everything suddenly works (text renders in any color I specify and pass to the function to initialize color4f) :


CGContextSetFillColorWithColor(context, CGColorCreate(colorSpace, color4f));


:confused:

The difference I see is that I pass the actual colorSpace I made earlier to the function, but since I created the context with that color space anyhow, shouldn't CGContextSetFillColor() be able to get it from there and create its own CGColorRef object ?

What's even the purpose of CGContextSetFillColor() if it doesn't work ?



xStep
Feb 23, 2012, 02:38 AM
I don't know, but you left out a critical item, the setup of color4f which may influence the response to you. Perhaps CGColorCreate is clamping the values to the nearest correct values.

PhoneyDeveloper
Feb 23, 2012, 07:22 AM
Your code will be simplified if you use the various UI convenience routines to create a context and colors. Use UIColors.CGColor etc.

KnightWRX
Feb 23, 2012, 07:26 AM
I don't know, but you left out a critical item, the setup of color4f which may influence the response to you. Perhaps CGColorCreate is clamping the values to the nearest correct values.

It's the same for both, that code didn't change nor did the values passed to it. Basically, I was passing it 0.0, 0.0, 0.0, 1.0 according to the debugger, both times.

I'd hope Apple uses the same code to manpilation the components. :confused: (CGContextSetFillColor() takes the same CGFloat components[] parameter than CGColorCreate() does, hence why I was able to just insert it as is).

But if it's going to help solve this question, here it is :

CGFloat color4f [] = { (color.components[COMPONENT_RED] / 0xff), (color.components[COMPONENT_GREEN] / 0xff), (color.components[COMPONENT_BLUE] / 0xff), (color.components[COMPONENT_ALPHA] / 0xff) };

color is defined as the following :

typedef union
{
unsigned char components[4];
uint32_t pixel;
} MyColorType

The constants are properly defined to point to the right channel yes (since it works with CGCreateColor after all ;) )

chown33
Feb 23, 2012, 07:52 AM
IBut if it's going to help solve this question, here it is :

CGFloat color4f [] = { (color.components[COMPONENT_RED] / 0xff), (color.components[COMPONENT_GREEN] / 0xff), (color.components[COMPONENT_BLUE] / 0xff), (color.components[COMPONENT_ALPHA] / 0xff) };

color is defined as the following :

typedef union
{
unsigned char components[4];
uint32_t pixel;
} MyColorType

The constants are properly defined to point to the right channel yes (since it works with CGCreateColor after all ;) )
In this expression:
(color.components[COMPONENT_RED] / 0xff)

The type of color.components[COMPONENT_RED] is char (one of the integer types, as distinct from floating-point or pointer types), and the type of 0xff is an integer compile-time constant. So the expression is evaluated using integer arithmetic.

Unfortunately for you, you need the expression to be evaluated in floating-point arithmetic, otherwise all values of char that are less than 0xff will produce a net result of 0. To understand why, think about what the runtime result would be for two integer constants:
int i = 35 / 0xff;
It should be clear that all values of the numerator below 0xff will result in 0. If the color you want is black, fine. But think about what effect that has on alpha.

The fix is to change your compile-time expression term from 0xff to 255.0. With a decimal point and a 0 fractional part, it's now a floating-point expression, and the compile-time operands are all promoted to floating-point (same as runtime).

KnightWRX
Feb 23, 2012, 10:50 AM
Your code will be simplified if you use the various UI convenience routines to create a context and colors. Use UIColors.CGColor etc.

Sure, but then everything else will break as I will no longer have direct access to my imagePixel[] array. I use it as a good old framebuffer, the goal of writing it the way I am is to replicate as close as possible the experience of writing graphics code for a time long forgotten.

I already feel dirty enough with UIFont and NSString's drawInRect.

The type of color.components[COMPONENT_RED] is char (one of the integer types, as distinct from floating-point or pointer types), and the type of 0xff is an integer compile-time constant. So the expression is evaluated using integer arithmetic.

You're right, though it still doesn't explain why CGContextSetFillColor() didn't work for black text :

White : R: 1.000000 G: 1.000000 B: 1.000000 A: 1.000000
Red : R: 0.000000 G: 0.000000 B: 0.000000 A: 1.000000
Black : R: 0.000000 G: 0.000000 B: 0.000000 A: 1.000000

Red obviously would have looked black if I had used it as a test case (all my test cases were white and black, with white working and black not working).

Casting the color.components[] to float value also works.

KnightWRX
Feb 23, 2012, 07:07 PM
chown33, you actually raised another point without making it. 0xff is nice as long as chars are 8 bit long. The portable way though would be to use the standard library's CHAR_MAX constant in order to always get a proper result.

So I edited the initialization to the following :

CGFloat color4f [] = { (color.components[COMPONENT_RED] / (CGFloat) CHAR_MAX), (color.components[COMPONENT_GREEN] / (CGFloat) CHAR_MAX), (color.components[COMPONENT_BLUE] / (CGFloat) CHAR_MAX), (color.components[COMPONENT_ALPHA] / (CGFloat) CHAR_MAX) };


Now it's portable and returning proper floating point values between 0.000000 and 1.000000 for each channel. I actually tested this time with colors other than white and black (never actually did, which is why I never noticed that while the result of the division was a floating point, the conversion was done after the actual math had taken place and the decimals had been dropped).

However, the code still renders the color properly using CGContextSetFillColorWithColor() but not with CGContextSetFillColor() even though I pass both those functions the current context (the one used to draw the string and the one created for my bitmap I'm rendering) and the same color4f array... :confused:

No one has any idea why ?