4

I have a greyscale gem top view.

(PNG format, so has alpha component)

I would like to create 12 small size buttons, each in a different colour, from this image.

For the sake of tidiness, I would like to do this within the code rather than externally in some art package.

Can anyone provide a method (or even some code) for doing this?

PS I am aware of how to do it in GL using a ridiculous amount of code, I'm hoping there is a simpler way using core graphics / core animation

EDIT: Working solution, thanks to awesomeness from below answer

    CGSize targetSize = (CGSize){100,100};
    UIImage* image;
    {
        CGRect rect = (CGRect){ .size = targetSize };

        UIGraphicsBeginImageContext( targetSize );
        {
            CGContextRef X = UIGraphicsGetCurrentContext();

            UIImage* uiGem = [UIImage imageNamed: @"GemTop_Dull.png"];

            // draw gem
            [uiGem drawInRect: rect];

            // overlay a red rectangle
            CGContextSetBlendMode( X, kCGBlendModeColor ) ;
            CGContextSetRGBFillColor ( X,  0.9, 0, 0,  1 );
            CGContextFillRect ( X, rect );

            // redraw gem 
            [uiGem drawInRect: rect
                    blendMode: kCGBlendModeDestinationIn
                        alpha: 1. ];

            image = UIGraphicsGetImageFromCurrentImageContext();
        }
        UIGraphicsEndImageContext();
    }
P i
  • 29,020
  • 36
  • 159
  • 267
  • I'm very curious about the seemingly superfluous {} in your code sample - what do these do – Rhubarb Sep 26 '12 at 12:23
  • They make code clearer / encapsulate variables – P i Oct 16 '12 at 16:42
  • If you want to support retina images, make sure you use `UIGraphicsBeginImageContextWithOptions(targetSize, NO, 0.0);` instead of `UIGraphicsBeginImageContext( targetSize );`. The parameters `NO` and `0.0` denote using an opaque context (YES or NO value) and a scale factor (0.0 for the device scale factor, any other value for an explicit scale factor), respectively. – Thomas Verbeek Mar 04 '14 at 23:35

3 Answers3

4

The easiest way to do it is to draw the image into an RGB-colorspaced CGBitmapContext, use CGContextSetBlendMode to set kCGBlendModeColor, and then draw over it with a solid color (e.g. with CGContextFillRect).

Anomie
  • 92,546
  • 13
  • 126
  • 145
  • I think this is just going to give the problem mentioned here: http://forums.macrumors.com/showthread.php?t=547339 – P i Jul 14 '11 at 07:55
  • 1
    If the `CGContextFillRect` obliterates the alpha channel, try drawing the original image over the top with `kCGBlendModeDestinationIn` to reapply it. – Anomie Jul 14 '11 at 10:54
  • I just tried that ( I have amended my question )... still I cannot get rid of them red background. Any ideas? – P i Jul 14 '11 at 15:37
  • 1
    @Pi: `drawInRect:` isn't respecting the blend mode you set on the underlying context. Try `drawInRect:blendMode:alpha:` instead. – Anomie Jul 14 '11 at 16:09
  • Amazing! Thankyou -- I would have never figured that out! – P i Jul 14 '11 at 16:49
1

The best looking results are going to come from using the gray value to index into a gradient that goes from the darkest to the lightest colors of the desired result. Unfortunately I don't know the specifics of doing that with core graphics.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • You can use [CIColorMap](https://developer.apple.com/library/ios/documentation/GraphicsImaging/Reference/CoreImageFilterReference/Reference/reference.html#//apple_ref/doc/uid/TP30000136-SW59) in the Core Image package to do this. – bcattle Jan 13 '14 at 06:23
  • See for example [this question](http://stackoverflow.com/questions/3182711/cicolormap-filter-error) – bcattle Jan 13 '14 at 07:13
0

This is an improvement upon the answer in the question and an implementation of @Anomie

First, put this at the beginning of your UIButton class, or your view controller. It translates from UIColor to an RGBA value, which you will need later.

typedef enum { R, G, B, A } UIColorComponentIndices;


@implementation UIColor (EPPZKit)

- (CGFloat)redRGBAValue {
    return CGColorGetComponents(self.CGColor)[R];
}

- (CGFloat)greenRGBAValue  {
    return CGColorGetComponents(self.CGColor)[G];
}

- (CGFloat)blueRGBAValue  {
    return CGColorGetComponents(self.CGColor)[B];
}

- (CGFloat)alphaRGBAValue  {
    return CGColorGetComponents(self.CGColor)[A];
}

@end

Now, make sure that you have your custom image button in IB, with a grayscale image and the right frame. This is considerably better and easier then creating the custom image button programmatically, because:

  • you can let IB load the image, instead of having to load it manually
  • you can adjust the button and see it visually in IB
  • your IB will look more like your app at runtime
  • you don't have to manually set frames

Assuming you are having the button be in IB (near the bottom will be support for having it programmatically created), add this method to your view controller or button cub class:

- (UIImage*)customImageColoringFromButton:(UIButton*)customImageButton fromColor:(UIColor*)color {
    UIImage *customImage = [customImageButton.imageView.image copy];

    UIGraphicsBeginImageContext(customImageButton.imageView.frame.size); {
        CGContextRef X = UIGraphicsGetCurrentContext();

        [customImage drawInRect: customImageButton.imageView.frame];

        // Overlay a colored rectangle
        CGContextSetBlendMode( X, kCGBlendModeColor) ;
        CGContextSetRGBFillColor ( X, color.redRGBAValue, color.greenRGBAValue, color.blueRGBAValue, color.alphaRGBAValue);
        CGContextFillRect ( X, customImageButton.imageView.frame);

        // Redraw
        [customImage drawInRect:customImageButton.imageView.frame blendMode: kCGBlendModeDestinationIn alpha: 1.0];

        customImage = UIGraphicsGetImageFromCurrentImageContext();
    }
    UIGraphicsEndImageContext();

    return customImage;
}

You then will need to call it in a setup method in your view controller or button subclass, and set the imageView of the button to it:

[myButton.imageView setImage:[self customImageColoringFromButton:myButton fromColor:desiredColor]];

If you are not using IB to create the button, use this method:

- (UIImage*)customImageColoringFromImage:(UIImage*)image fromColor:(UIColor*)color fromFrame:(CGRect)frame {
    UIImage *customImage = [image copy];

    UIGraphicsBeginImageContext(frame.size); {
        CGContextRef X = UIGraphicsGetCurrentContext();

        [customImage drawInRect: frame];

        // Overlay a colored rectangle
        CGContextSetBlendMode( X, kCGBlendModeColor) ;
        CGContextSetRGBFillColor ( X, color.redRGBAValue, color.greenRGBAValue, color.blueRGBAValue, color.alphaRGBAValue);
        CGContextFillRect ( X, frame);

        // Redraw
        [customImage drawInRect:frame blendMode: kCGBlendModeDestinationIn alpha: 1.0];

        customImage = UIGraphicsGetImageFromCurrentImageContext();
    }
    UIGraphicsEndImageContext();

    return customImage;
}

And call it with:

[self.disclosureButton.imageView setImage:[self customImageColoringFromImage:[UIImage imageNamed:@"GemTop_Dull.png"] fromColor:desiredColor fromFrame:desiredFrame]];
Eliza Wilson
  • 1,031
  • 1
  • 13
  • 38