I am working on an app - or rather on some re-usable "framework" which I am happy to share once it works. Within this app the user should be able to choose from a list of color themes. Therefore the app must be able to tint its UI elements in some rather dynamic way.
For Buttons all the tinting does not work. Properly tinted background images must be supplied here. But preparing one set of background images for each them is just second best. It is not dynamic and flexible enough.
In the end a solution may come down to providing one monochrome (grey) gradiented image for the selected and normal state and tint that image programmatically using CoreGraphics or OpenGL. But frankly, I do not know where to start there. How should the gradient look like and how would I then programmatically tint that in any given color?
Pretty much applies to UISegmentedControls, just a bit more complicated. :) Any generic solution that covers UISegementedControls too is highliy appreciated.
In iOS7 you can use the method imagedWithRenderingMode: in conjunction with the method setTintColor:
But, be sure to use UIImageRenderingModeAlwaysTemplate as it replaces all non-transparent colors on a UIImage with the tint color. The default is UIImageRenderingModeAutomatic.
UIImageRenderingModeAlwaysTemplate
Always draw the image as a template image, ignoring its color information.
Objective-C:
UIImage * image = [myButton.currentImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
[myButton setImage:image forState:UIControlStateNormal];
Swift:
let image = myButton.currentImage?.withRenderingMode(.alwaysTemplate)
myButton.setImage(image, for: .normal)
I made a UIImage category following another post that i cant find that takes the image and tints it as follows:
- (UIImage *)tintedImageWithColor:(UIColor *)tintColor {
UIGraphicsBeginImageContextWithOptions(self.size, NO, [[UIScreen mainScreen] scale]);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 0, self.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
// draw alpha-mask
CGContextSetBlendMode(context, kCGBlendModeNormal);
CGContextDrawImage(context, rect, self.CGImage);
// draw tint color, preserving alpha values of original image
CGContextSetBlendMode(context, kCGBlendModeSourceIn);
[tintColor setFill];
CGContextFillRect(context, rect);
UIImage *coloredImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return coloredImage;
}
This will take the image and fill in all of the areas with an alpha value with the given color.
I created a UIButton category using horsejockey's code:
https://github.com/filipstefansson/UITintedButton
Here's the new methods:
-(void)setImageTintColor:(UIColor *)color;
-(void)setBackgroundTintColor:(UIColor *)color forState:(UIControlState)state;
+(void)tintButtonImages:(NSArray *)buttons withColor:(UIColor *)color;
+(void)tintButtonBackgrounds:(NSArray *)buttons withColor:(UIColor *)color forState:(UIControlState)state;
Here is a partial answer to your question. This is a helper method I wrote that tints a grey scale image:
// baseImage is the grey scale image. color is the desired tint color
+ (UIImage *)tintImage:(UIImage *)baseImage withColor:(UIColor *)color {
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);
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 the original image was stretchable, make the new image stretchable
if (baseImage.leftCapWidth || baseImage.topCapHeight) {
newImage = [newImage stretchableImageWithLeftCapWidth:baseImage.leftCapWidth topCapHeight:baseImage.topCapHeight];
}
return newImage;
}
I modifie some things from "horsejockey":
+(UIImage *)getTintedImage:(UIImage*)image withColor:(UIColor *)tintColor
{
UIGraphicsBeginImageContextWithOptions(image.size, NO, [[UIScreen mainScreen] scale]);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 0, image.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGRect rect = CGRectMake(0, 0, image.size.width, image.size.height);
// draw alpha-mask
CGContextSetBlendMode(context, kCGBlendModeNormal);
CGContextDrawImage(context, rect, image.CGImage);
// draw tint color, preserving alpha values of original image
CGContextSetBlendMode(context, kCGBlendModeSourceIn);
[tintColor setFill];
CGContextFillRect(context, rect);
UIImage *coloredImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return coloredImage;
}
I'm refering you to a SO question that seems to be what you are looking for and it's got an answer.
How to tint a transparent PNG image in iPhone?
Related
I want to do the following:
As you can see one image (selected) does not have a grey fade and the other (nonselected item) does not.
I have tried multiple solutions. Including coloring the images with a grey color
- (UIImage *)colorizeImage:(UIImage *)image withColor:(UIColor *)color {
UIGraphicsBeginImageContext(image.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect area = CGRectMake(0, 0, image.size.width, image.size.height);
CGContextScaleCTM(context, 1, -1);
CGContextTranslateCTM(context, 0, -area.size.height);
CGContextSaveGState(context);
CGContextClipToMask(context, area, image.CGImage);
[color set];
CGContextFillRect(context, area);
CGContextRestoreGState(context);
CGContextSetBlendMode(context, kCGBlendModeMultiply);
CGContextDrawImage(context, area, image.CGImage);
UIImage *colorizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return colorizedImage;
}
However with this approach I run into an issue where if the bg of an image has a transparency the white below it shine through and it looks strange:
How can I detect the transparency and change the transparency to white? Or is there a better solution for doing this?
image = [self imageWithImage:image scaledToSize:CGSizeMake((self.collectionViewContainer.frame.size.width/3),90)];
UIImage *unSelected = [self colorizeImage:image withColor:[[UIColor grayColor] colorWithAlphaComponent:.9]];
UIImageView *imgView = [[UIImageView alloc] initWithImage:unSelected highlightedImage:image];
Since you simply want to apply a gray tint to an image, try it this way:
- (UIImage *)colorizeImage:(UIImage *)image withColor:(UIColor *)color {
UIGraphicsBeginImageContext(image.size, NO, image.scale);
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect area = CGRectMake(0, 0, image.size.width, image.size.height);
[image drawInRect:area];
[color set];
CGContextFillRect(context, area);
UIImage *colorizedImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return colorizedImage;
}
This draws the image and then draws the color over the image. Assuming the color is partially transparent, the entire new image will be colored.
As you had it originally, you were only coloring the non-transparent parts due to the image clipping.
This update also applies the proper scale to the new image to match the original image.
I am having problem understanding the reason for the following method, to return images that are visibly pixelated. I have double checked the size of the image, and it is fine. What's more, without tinting it, the image edges are smooth, and lack pixelation.
The method for tinting image, based on IOS7 ImageView's tintColor property, works fine, however I would love to find out what is wrong with the following code, because it seems to work for everybody but me. Thanks!
- (UIImage *)imageTintedWithColor:(UIColor *)color
{
if (color) {
UIImage *img = self; // The method is a part of UIImage category, hence the "self"
UIGraphicsBeginImageContext(img.size);
// get a reference to that context we created
CGContextRef context = UIGraphicsGetCurrentContext();
// set the fill color
[color setFill];
// translate/flip the graphics context (for transforming from CG* coords to UI* coords
CGContextTranslateCTM(context, 0, img.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
// set the blend mode to color burn, and the original image
CGContextSetBlendMode(context, kCGBlendModeColorBurn);
CGRect rect = CGRectMake(0, 0, img.size.width, img.size.height);
CGContextDrawImage(context, rect, img.CGImage);
// set a mask that matches the shape of the image, then draw (color burn) a colored rectangle
CGContextSetBlendMode(context, kCGBlendModeSourceIn);
CGContextAddRect(context, rect);
CGContextDrawPath(context,kCGPathFill);
// generate a new UIImage from the graphics context we drew onto
UIImage *coloredImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//return the color-burned image
return coloredImg;
}
return self;
}
Change this line:
UIGraphicsBeginImageContext(img.size);
to:
UIGraphicsBeginImageContextWithOptions(img.size, NO, 0);
If your images will never have an transparency, change the NO to YES.
I am using the below code to color the UIImage and it is working fine.
But the only problem I am facing is that it is slow.
I can run this in background thread but for some reason I want to color the image in main thread.
Is there any other solution which can color the UIImage faster than this solution. Could CIContext help? I would appreciate any help.
UIImage *coloredImage;
UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 0, self.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
CGContextSetBlendMode(context, kCGBlendModeNormal);
[tintColor setFill];
CGContextFillRect(context, rect);CGContextSetBlendMode(context, kCGBlendModeMultiply);
CGContextDrawImage(context, rect, self.CGImage);
CGContextSetBlendMode(context, kCGBlendModeDestinationIn);
CGContextDrawImage(context, rect, self.CGImage);
CGContextFlush(context);
coloredImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
It appears as though you are setting a context size, then filling that size with a color and drawing your image on top of that color. That means your image has transparency in it. Have you tried just drawing your image and setting the UIImageView's background color? The background color will be shown behind the entire image, therefore only being visible where your image has transparency. That should work.
I'm trying to change the color of an image at runtime. I've seen a couple answers on SO, but they all change the background color and not the foreground, which is what I am trying to do. I've based my code on another SO thread.
Here's the code I have:
#implementation UIImage (Coloring)
-(UIImage*) imageWithColorOverlay:(UIColor*)color
{
//create context
UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
CGContextRef context = UIGraphicsGetCurrentContext();
// drawingcode
//bg
CGRect rect = CGRectMake(0.0, 0.0, self.size.width, self.size.height);
[self drawInRect:rect];
//fg
CGContextSetBlendMode(context, kCGBlendModeMultiply);
CGContextSetFillColorWithColor(context, color.CGColor);
CGContextFillRect(context, rect);
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//end
return image;
}
#end
This are the results thus far:
From left to right:
No blending, just the normal asset. The gray bg is from the UIView behind the imageviews, the bg of the image is transparent.
Multiply with kCGBlendModeMultiply
Color burn with kCGBlendModeColorBurn
Is there an CGBlendMode that'll achieve replacing the foreground color? Also, is it possible to replace both the foreground color(white) and the shadow(black)?
After messing around with the different blend options, this code did the trick. The only caveat is that the red tint is shown in the shadow, its not technically correct but its close enuf
#implementation UIImage (Coloring)
-(UIImage*) imageWithColorOverlay:(UIColor*)color
{
//create context
UIGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
CGContextRef context = UIGraphicsGetCurrentContext();
//drawingcode
//bg
CGRect rect = CGRectMake(0.0, 0.0, self.size.width, self.size.height);
[self drawInRect:rect];
//fg
CGContextSetBlendMode(context, kCGBlendModeMultiply);
CGContextSetFillColorWithColor(context, color.CGColor);
CGContextFillRect(context, rect);
//mask
[self drawInRect:rect blendMode:kCGBlendModeDestinationIn alpha:1.0];
//end
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
I'm working on an App which works with gestureRecognizer. With gestures it is possible to select an UIImage (such as rectangle.png) and it is possible with a UIPopoverView to change the color of that image by selecting a color for the selected image.
This image lay in a UIImageView and I think the best solution is to mask that image and set a colored image with the dame size and frame instead.
Is it the right way? How can I optimize my approach?
Which could be the best practice for this requirement?
(UIImage *)maskImage:(UIColor *)maskColor
{
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
UIGraphicsBeginImageContext(rect.size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(context, 0, rect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextClipToMask(context, rect, self.CGImage);
CGContextSetFillColorWithColor(context, maskColor.CGColor);
CGContextFillRect(context, rect);
UIImage *smallImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return smallImage;
}