Why are HSB values different when querying them than when drawing?

238 Views Asked by At

First, I created this utility in order to debug my situation:

+(NSString *)hsbaFromColor:(UIColor *)c
{
   CGFloat hue, saturation, brightness, alpha;
   NSString *returnValue;
   if ([c getHue:&hue saturation:&saturation brightness:&brightness alpha:&alpha]) {
      returnValue = [NSString stringWithFormat:@"H:%f S:%f B:%f A:%f",hue,saturation,brightness,alpha];
   }
   return returnValue;
}

This utility is used in order to confirm I am not getting an expected set of data back when reading values just drawn. I expect to get value matching what was just drawn. Here is the drawing code:

CGContextSetLineCap(self.context, kCGLineCapRound);
CGContextSetStrokeColorWithColor(self.context, ant.color.CGColor);
CGContextSetLineWidth(self.context, ant.size.width*1.0);
CGContextMoveToPoint(self.context, ant.previousLocation.x, ant.previousLocation.y);
CGContextAddLineToPoint(self.context, ant.location.x, ant.location.y);
CGContextStrokePath(self.context);
NSLog(@"Draw color: %@",[ColorTools hsbaFromColor:ant.color]);
UIColor *colorJustDrawn = [self getColorFromContextAtPosition:ant.location];
NSLog(@"Read color: %@",[ColorTools hsbaFromColor:colorJustDrawn]);

In my test case ant.size.width is 5. That should produce a line drawn wide enough that when I query the color, I should get the same color back. Here is the method for querying color:

-(UIColor *)getColorFromContextAtPosition:(CGPoint)p
{
   if (p.x < self.minX) {
      self.minX = p.x;
   }
   if (p.x > self.maxX) {
      self.maxX = p.x;
   }
   if (p.y < self.minY) {
      self.minY = p.y;
   }
   if (p.y > self.maxY) {
      self.maxY = p.y;
   }
   CGFloat screenScale = [[UIScreen mainScreen] scale];
   CGSize contextSize;
   contextSize.width = CGBitmapContextGetWidth(self.context);
   contextSize.height = CGBitmapContextGetHeight(self.context);
   unsigned char* data = CGBitmapContextGetData (self.context);
   UIColor *color = [UIColor whiteColor];
   if (data != NULL) {
        //offset locates the pixel in the data from x,y.
        //4 for 4 bytes of data per pixel, w is width of one row of data.
        int offset = 4*((contextSize.width*floorf(p.y*screenScale))+floorf(p.x*screenScale));
        int alpha =  data[offset+3];
        int red = data[offset+0];
        int green = data[offset+1];
        int blue = data[offset+2];
        color = [UIColor colorWithRed:(red/255.0f) green:(green/255.0f) blue:(blue/255.0f) alpha:(alpha/255.0f)];
   }
   return color;
}

I realize this method is creating a color from a different mode, RGB, than what was written, HSB, but since I'm using the same context for both, I would think this shouldn't matter. Maybe there's some other setting I need to set?

Here is the method that creates the context:

-(void)blankOutImageView{
   CGRect frame = self.mainImageView.bounds;
   UIGraphicsBeginImageContextWithOptions(frame.size, YES, 0.0);
   CGContextRef context = UIGraphicsGetCurrentContext();
   CGContextSetLineWidth(context, 1.0);
   CGContextSetFillColorWithColor(context, [UIColor grayColor].CGColor);
   CGContextSetStrokeColorWithColor(context, [UIColor grayColor].CGColor);
   CGContextMoveToPoint(context, 0.0, 0.0);
   CGContextAddRect(context, frame);
   CGContextFillRect(context, frame);
   CGContextStrokePath(context);
   CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
   CGContextStrokePath(context);

   UIImage *blank = UIGraphicsGetImageFromCurrentImageContext();
   self.context = context;
   self.mainImageView.image = blank;
}

Here's a sampling of some data. The app selects random colors, alternating the hue by adding the golden ratio each time a new color is needed and then normalizing to 0..1. In the run I just did, the color created was:

H:0.537255 S:0.598039 B:1.000000 A:1.000000

created using the following code:

self.lightColor = [UIColor colorWithHue:hue saturation:saturation brightness:1.0 alpha:1.0];

The values of hue and saturation are created at initialization time of the ant but remain constant throughout that object's life. In any case, here is output from drawing and querying using a color that appears light cyan-blue on the screen:

Draw color: H:0.537255 S:0.598039 B:0.995069 A:1.000000
Draw color: R:0.399979 G:0.862049 B:0.995069 A:1.000000

Read color: H:0.129386 S:0.598425 B:0.996078 A:1.000000
Read color: R:0.996078 G:0.862745 B:0.400000 A:1.000000

Hmmm. Now that I look at it, it seems there may be an endian issue. Looks like red and blue are backward.

1

There are 1 best solutions below

0
On

Changing this method resolves the problem.

-(UIColor *)getColorFromContextAtPosition:(CGPoint)p
{
   if (p.x < self.minX) {
      self.minX = p.x;
   }
   if (p.x > self.maxX) {
      self.maxX = p.x;
   }
   if (p.y < self.minY) {
      self.minY = p.y;
   }
   if (p.y > self.maxY) {
      self.maxY = p.y;
   }
   CGFloat screenScale = [[UIScreen mainScreen] scale];
   CGSize contextSize;
   contextSize.width = CGBitmapContextGetWidth(self.context);
   contextSize.height = CGBitmapContextGetHeight(self.context);
   unsigned char* data = CGBitmapContextGetData (self.context);
   UIColor *color = [UIColor whiteColor];
   if (data != NULL) {
        //offset locates the pixel in the data from x,y.
        //4 for 4 bytes of data per pixel, w is width of one row of data.
        int offset = 4*((contextSize.width*floorf(p.y*screenScale))+floorf(p.x*screenScale));
        int alpha =  data[offset+3];
        int red = data[offset+2];       //Note red and blue are reversed.
        int green = data[offset+1];
        int blue = data[offset+0];      //
        color = [UIColor colorWithRed:(red/255.0f) green:(green/255.0f) blue:(blue/255.0f) alpha:(alpha/255.0f)];
   }
   return color;
}

I don't remember where I got the idea for this code. Whatever it was, the alpha channel was also on the other end. When I moved the alpha channel, I should have checked the other channels, too, but I didn't.

If anyone decides to borrow this code, you should know that the channel order is not the same on the device as in the simulator. What I'm looking for now is a way to tell what the appropriate order is.