Search code examples
iphonecore-graphics

Displaying and array of colors using CGShading


I have an array of CGColors that I need to display across a path. I tried doing this with a CGGradient, but I don't want the colors the blend between between values. It looks like the best solution would be to use a GGShading object, but I am having trouble figuring out exactly how they work. I'm mainly confused about what I need to have for the CGFunction input for the CGShading.

Can someone point me in the right direction on what I would need to make this CGFunction look like to to simply display an array go CGColors on a specified CGPath?

Thanks!


Solution

  • Perhaps a little late, so I hope this is still of use to you. I've listed the code for a simple UIView subclass that draws a circle using the shading you described. The code with comments should be self-explanatory.

    @implementation CGShadingCircle
    
    
    // This is the callback of our shading function.
    // info:    a pointer to our NSMutableArray of UIColor objects
    // inData:  contains a single float that gives is the current position within the gradient
    // outData: we fill this with the color to display at the given position
    static void CGShadingCallback(void* info, const float* inData, float* outData) {
        // Our colors
        NSMutableArray* colors = (NSMutableArray*)info;
        // Position within the gradient, ranging from 0.0 to 1.0
        CGFloat position = *inData;
    
        // Find the color that we want to used based on the current position;
        NSUInteger colorIndex = position * [colors count];
    
        // Account for the edge case where position == 1.0
        if (colorIndex >= [colors count])
            colorIndex = [colors count] - 1;
    
        // Get our desired color from the array
        UIColor* color = [colors objectAtIndex:colorIndex];
    
        // Copy the 4 color components (red, green, blue, alpha) to outData
        memcpy(outData, CGColorGetComponents(color.CGColor), 4 * sizeof(CGFloat));  
    }
    
    
    // Set up our colors and shading function
    - (void)initInternal {  
        _colors = [[NSMutableArray alloc] init];
    
        // Creating the colors in this way ensures that the underlying color space is UIDeviceRGBColorSpace
        // and thus has 4 color components: red, green, blue, alpha
        [_colors addObject:[UIColor colorWithRed:1.0f green:0.0f blue:0.0f alpha:1.0f]]; // Red
        [_colors addObject:[UIColor colorWithRed:0.0f green:1.0f blue:0.0f alpha:1.0f]]; // Green
        [_colors addObject:[UIColor colorWithRed:0.0f green:0.0f blue:1.0f alpha:1.0f]]; // Blue
        [_colors addObject:[UIColor colorWithRed:1.0f green:1.0f blue:1.0f alpha:1.0f]]; // White
        [_colors addObject:[UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:1.0f]]; // Black
    
        // Define the shading callbacks
        CGFunctionCallbacks callbacks;
        callbacks.version = 0;                      // Defaults to 0
        callbacks.evaluate = CGShadingCallback;     // This is our color selection function
        callbacks.releaseInfo = NULL;               // Not used
    
        // As input to our function we want 1 value in the range [0.0, 1.0].
        // This is our position within the 'gradient'.
        size_t domainDimension = 1;
        CGFloat domain[2] = {0.0f, 1.0f};
    
        // The output of our function is 4 values, each in the range [0.0, 1.0].
        // This is our selected color for the input position.
        // The 4 values are the red, green, blue and alpha components.
        size_t rangeDimension = 4;
        CGFloat range[8] = {0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f, 1.0f};
    
        // Create the shading function
        _shadingFunction = CGFunctionCreate(_colors, domainDimension, domain, rangeDimension, range, &callbacks);   
    }
    
    - (id)initWithFrame:(CGRect)frame {
        self = [super initWithFrame:frame];
        if (self) {
            [self initInternal];
        }
        return self;
    }
    
    - (id)initWithCoder:(NSCoder*)decoder {
        self = [super initWithCoder:decoder];
        if (self) {
            [self initInternal];
        }
        return self;
    }
    
    - (void)dealloc {
        [_colors release];
        CGFunctionRelease(_shadingFunction);
    
        [super dealloc];
    }
    
    - (void)drawRect:(CGRect)rect {
        CGRect b = self.bounds;
        CGContextRef ctx = UIGraphicsGetCurrentContext();
    
        // Create a simple elliptic path
        CGContextAddEllipseInRect(ctx, b);
        // Set the current path as the clipping path
        CGContextClip(ctx);
    
        // Create our shading using the function that was defined earlier.
        CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB();
        CGShadingRef shading = CGShadingCreateAxial(colorspace,
            CGPointMake(CGRectGetMinX(b), CGRectGetMidY(b)),
            CGPointMake(CGRectGetMaxX(b), CGRectGetMidY(b)),
            _shadingFunction,
            true,
            true);
    
        // Draw the shading
        CGContextDrawShading(ctx, shading);
    
    
        // Cleanup
        CGShadingRelease(shading);
        CGColorSpaceRelease(colorspace);
    }
    
    @end
    

    This gives me the following output:

    I hope this helps!