Tinting UIImage to a different color, OR, generating UIImage from vector - ios

I have a circle with a black outline, and a white fill that I need to programmatically make the white into another color (via a UIColor). I've tried a handful of other stackoverflow solutions but none of them seem to work correctly, either filling just the outside or an outline.
I have two ways I could do this but I am unsure of how I would get the right results:
Tint just the white color to whatever the UIColor should be,
or,
Make a UIImage from two circles, one being filled and one overlapping that with black.

If you decide to use two circles, one white and one black, then you may find this helpful. This method will tint a uiimage one for you but it addresses the problem of only tinting the opaque part, meaning it will only tint the circle if you provide a png with transparency around the circle. So instead of filling the entire 24x24 frame of the image it fills only the opaque parts. This isn't exactly your question but you'll probably come across this problem if you go with the second option you listed.
-(UIImage*)colorAnImage:(UIColor*)color :(UIImage*)image{
CGRect rect = CGRectMake(0, 0, image.size.width, image.size.height);
UIGraphicsBeginImageContextWithOptions(rect.size, NO, image.scale);
CGContextRef c = UIGraphicsGetCurrentContext();
[image drawInRect:rect];
CGContextSetFillColorWithColor(c, [color CGColor]);
CGContextSetBlendMode(c, kCGBlendModeSourceAtop);
CGContextFillRect(c, rect);
UIImage *result = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return result;
}

Extend a UIView and just implement the drawRect method. For example, this will draw a green circle with a black outline.
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef gc = UIGraphicsGetCurrentContext();
[[UIColor greenColor] setFill];
CGContextFillEllipseInRect(gc, CGRectMake(0,0,24,24));
[[UIColor blackColor] set];
CGContextStrokeEllipseInRect(gc, CGRectMake(0,0,24,24));
}

For such simple shapes, just use CoreGraphics to draw a square and a circle -- adding the ability to set the fill color in your implementation.
If it's just black and white - then altering the white to another color is not so difficult when you know the color representations. Unfortunately, this is more complex to write and execute so… my recommendation is to just go straight to CoreGraphics for the simple task you outlined (bad pun, sorry).
here's a quick demo:
static void InsetRect(CGRect* const pRect, const CGFloat pAmount) {
const CGFloat halfAmount = pAmount * 0.5f;
*pRect = CGRectMake(pRect->origin.x + halfAmount, pRect->origin.y + halfAmount, pRect->size.width - pAmount, pRect->size.height - pAmount);
}
static void DrawBorderedCircleWithWidthInContext(const CGRect pRect, const CGFloat pWidth, CGContextRef pContext) {
CGContextSetLineWidth(pContext, pWidth);
CGContextSetShouldAntialias(pContext, true);
CGRect r = pRect;
/* draw circle's border */
CGContextSetRGBStrokeColor(pContext, 0.8f, 0.7f, 0, 1);
InsetRect(&r, pWidth);
CGContextStrokeEllipseInRect(pContext, r);
/* draw circle's fill */
CGContextSetRGBFillColor(pContext, 0, 0, 0.3f, 1);
InsetRect(&r, pWidth);
CGContextFillEllipseInRect(pContext, r);
}

Related

Set background - centered and not stretched

The programm sets the background as an image through:
[backgroundViewProxy setBackgroundColor:[UIColor colorWithPatternImage:[theme backgroundImage]]];
The drawRect of the correspoinding UIView is:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGSize phase = self.backgroundShift; //set this property to affect the positioning of the background image
CGContextSetPatternPhase(context, phase);
CGColorRef color = self.backgroundColor.CGColor;
CGContextSetFillColorWithColor(context, color);
CGContextFillRect(context, self.bounds);
CGContextRestoreGState(context);
}
I did not wrote the code on my own. I have to modify it.
I do understand what is going on here, but i cannot find a way to solve this problem:
I want the image to be centered on the screen (with and height) and it should not be streched or repeated.
At the moment, the image is repeated to fill the screen.
I have searched around the internet, but did not find a clear solution for centering(or positioning) the image in a similar context like this.
I would be glad if someone could help.
From apple's docs:
+ (UIColor *)colorWithPatternImage:(UIImage *)image
You can use pattern colors to set the fill or stroke color just as you would a solid color. During drawing, the image in the pattern color is tiled as necessary to cover the given area.
An alternative could be setting view.layer.contents to you image by casting it as type (id)

Fill UINavigationBar background with CGGradientRef

I would like to fill my UINavigationBar background with CGGradientRef instead of a picture file (e.g. myBackground.png). This practice will avoid having to create a PNG file for each screen size and also save storage space.
I've seen that it's possible to create an UIImage drawing a gradient from scratch and using:
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
Also, I've seen that I can assign an UIImage to a UINavigationBar using:
myNavigationController.navigationBar.barTintColor = [UIColor colorWithPatternImage:image]];
However, I have not been able to put these two together. Some help will be appreciated. Thank you.
Here is a overloaded UINavigationBar class using a two point gradient, although it could easily be improved to cover multi-point gradients.
To enable this bar style, select the navigation bar object in the navigation scene of the storyboard, and set its custom class to GradientNavigationBar.
In this case the awakeFromNib call is used to change the background, (assuming that the Navigation bar class has been changed in the storyboard), in the case that the Navigation bar is instantiated programatically, the customization call should be made in the appropriate position in the code.
The solution works by converting the colors passed to it to an array of CGFloat, then generating a CGGradientRef object, using those colors, creating an image and then using the setBackgroundImage:forBarMetrics call to set the background as required.
#interface GradientNavigationBar
#end
#implementation GradientNavigationBar
-(void) awakeFromNib {
[self setGradientBackground:[UIColor redColor]
endColor:[UIColor yellowColor]];
}
-(void) setGradientBackground:(UIColor *) startColor endColor:(UIColor *) endColor {
// Convert the colors into a format where they can be used with
// core graphics
CGFloat rs, gs, bs, as, re, ge, be, ae;
[startColor getRed:&rs green:&gs blue:&bs alpha:&as];
[endColor getRed:&re green:&ge blue:&be alpha:&ae];
CGFloat colors [] = {
rs, gs, bs, as,
re, ge, be, ae
};
// Generate an Image context with the appropriate options, it may
// be enhanced to take into account that Navbar heights differ
// eg between landscape and portrait in the iPhone
UIGraphicsBeginImageContextWithOptions(self.bounds.size, NO, 0.0);
CGContextRef gc = UIGraphicsGetCurrentContext();
CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB();
// The gradient element indicates the colors to be used and
// the color space
CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace, colors, NULL, 2);
CGColorSpaceRelease(baseSpace), baseSpace = NULL;
// Draw the gradient
CGContextDrawLinearGradient(gc, gradient, CGPointMake(0, 0),CGPointMake(0, self.bounds.size.height),0);
// Capture the image
UIImage * backgroundImage = UIGraphicsGetImageFromCurrentImageContext();
// The critical call for setting the background image
// Note that separate calls can be made e.g. for the compact
// bar metric.
[self setBackgroundImage:backgroundImage forBarMetrics:UIBarMetricsDefault];
CGGradientRelease(gradient), gradient = NULL;
UIGraphicsEndImageContext();
}
#end

Change color of detail disclosure button dynamically in code

I know similar question(s) have been asked before, but most answers say create a new detail disclosure button image with the required color (e.g. How to change color of detail disclosure button for table cell).
I want to be able to change it to any color I choose, dynamically, at run-time, so using a pre-configured image is not viable.
From reading around, I think there may be a few ways to do this, but I'm not sure which is the best, or exactly how to do it:
Draw the required color circle in code and overlay with an image of shadow round outside of circle and arrow right (with clear alpha channel for rest, so drawn color circle is still visible)
Add image of shadow round outside of circle in UIImageView, and using as a mask, flood fill within this shadow circle, then overlay the arrow.
Add greyscale image, mask it with itself, and overlay the required color (e.g. http://coffeeshopped.com/2010/09/iphone-how-to-dynamically-color-a-uiimage), then overlay that with arrow image.
What is the best way, and does anyone have any code showing exactly how to do it ?
I use the following helper method to tint a grayscale image. It takes the grayscale image, the tint color, and an option mask image used to ensure the tint only happens in part of the grayscale image. This method also ensure the new image has the same (if any) resizable insets as the original image.
+ (UIImage *)tintImage:(UIImage *)baseImage withColor:(UIColor *)color mask:(UIImage *)maskImage {
UIGraphicsBeginImageContextWithOptions(baseImage.size, NO, baseImage.scale);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGRect area = CGRectMake(0, 0, baseImage.size.width, baseImage.size.height);
CGContextScaleCTM(ctx, 1, -1);
CGContextTranslateCTM(ctx, 0, -area.size.height);
CGContextSaveGState(ctx);
if (maskImage) {
CGContextClipToMask(ctx, area, maskImage.CGImage);
} else {
CGContextClipToMask(ctx, area, baseImage.CGImage);
}
[color set];
CGContextFillRect(ctx, area);
CGContextRestoreGState(ctx);
CGContextSetBlendMode(ctx, kCGBlendModeOverlay);
CGContextDrawImage(ctx, area, baseImage.CGImage);
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
if (!UIEdgeInsetsEqualToEdgeInsets(baseImage.capInsets, UIEdgeInsetsZero)) {
newImage = [newImage resizableImageWithCapInsets:baseImage.capInsets];
}
return newImage;
}
Here is the non-retina grayscale detail disclosure image:
Here is the retina version:
Here is the non-retina mask (between the quotes - it's mostly white):
""
And the retina mask (between the quotes:
""
Consider also using an icon font in a label instead of an image. Something like glyphicons. Then you can set the text colour to whatever you want. You also have good options for scaling. The cost is that you don't have quite such precise control in some cases but it should work well for your case.

Merge two PNG UIImages in iOS without losing transparency

I have two png format images and both have transparency defined. I need to merge these together into a new png image but without losing any of the transparency in the result.
+(UIImage *) combineImage:(UIImage *)firstImage colorImage:(UIImage *)secondImage
{
UIGraphicsBeginImageContext(firstImage.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextTranslateCTM(context, 0, firstImage.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGRect rect = CGRectMake(0, 0, firstImage.size.width, firstImage.size.height);
// draw white background to preserve color of transparent pixels
CGContextSetBlendMode(context, kCGBlendModeDarken);
[[UIColor whiteColor] setFill];
CGContextFillRect(context, rect);
CGContextSaveGState(context);
CGContextRestoreGState(context);
// draw original image
CGContextSetBlendMode(context, kCGBlendModeDarken);
CGContextDrawImage(context, rect, firstImage.CGImage);
// tint image (loosing alpha) - the luminosity of the original image is preserved
CGContextSetBlendMode(context, kCGBlendModeDarken);
//CGContextSetAlpha(context, .85);
[[UIColor colorWithPatternImage:secondImage] setFill];
CGContextFillRect(context, rect);
CGContextSaveGState(context);
CGContextRestoreGState(context);
// mask by alpha values of original image
CGContextSetBlendMode(context, kCGBlendModeDestinationIn);
CGContextDrawImage(context, rect, firstImage.CGImage);
// image drawing code here
CGContextRestoreGState(context);
UIImage *coloredImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return coloredImage;
}
needed any help to improve my code in performance.
Thanks in advance
First of all, those calls to CGContextSaveGState and CGContextRestoreGState, one after the other with nothing in between, isn't doing anything for you. See this other answer for an explanation of what CGContextSaveGState and CGContextRestoreGState do: CGContextSaveGState vs UIGraphicsPushContext
Now, it's not 100% clear to me what you mean by "merging" the images. If you just want to draw one on top of the other, and blend their colors using a standard blending mode then you just need to change those blend mode calls to pass kCGBlendModeNormal (or just leave out the calls to CGContextSetBlendMode entirely. If you want to mask the second image by the first image's alpha value then you should draw the second image with the normal blend mode, then switch to kCGBlendModeDestinationIn and draw the first image.
I'm afraid I'm not really sure what you're trying to do with the image tinting code in the middle, but my instinct is that you won't end up needing it. You should be able to get most merging effects by just drawing one image, then setting the blending mode, then drawing the other image.
Also, the code you've got there under the comment "draw white background to preserve color of transparent pixels" might draw white through the whole image, but it certainly doesn't preserve the color of transparent pixels, it makes those pixels white! You should remove that code too unless you really want your "transparent" color to be white.
Used the code given in Vinay's question and Aaron's comments to develop this hybrid that overlays any number of images:
/**
Returns the images overplayed atop each other according to their array position, with the first image being bottom-most, and the last image being top-most.
- parameter images: The images to overlay.
- parameter size: The size of resulting image. Any images not matching this size will show a loss in fidelity.
*/
func combinedImageFromImages(images: [UIImage], withSize size: CGSize) -> UIImage
{
// Setup the graphics context (allocation, translation/scaling, size)
UIGraphicsBeginImageContext(size)
let context = UIGraphicsGetCurrentContext()
CGContextTranslateCTM(context, 0, size.height)
CGContextScaleCTM(context, 1.0, -1.0)
let rect = CGRectMake(0, 0, size.width, size.height)
// Combine the images
for image in images {
CGContextDrawImage(context, rect, image.CGImage)
}
let combinedImage = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return combinedImage
}

iOS: Applying a RGB filter to a greyscale PNG

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();
}
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).
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.
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]];

Resources