I would like to tint an image with a color reference. The results should look like the Multiply blending mode in Photoshop, where whites would be replaced with tint:
I will be changing the color value continuously.
Follow up: I would put the code to do this in my ImageView's drawRect: method, right?
As always, a code snippet would greatly aid in my understanding, as opposed to a link.
Update: Subclassing a UIImageView with the code Ramin suggested.
I put this in viewDidLoad: of my view controller:
[self.lena setImage:[UIImage imageNamed:kImageName]];
[self.lena setOverlayColor:[UIColor blueColor]];
[super viewDidLoad];
I see the image, but it is not being tinted. I also tried loading other images, setting the image in IB, and calling setNeedsDisplay: in my view controller.
Update: drawRect: is not being called.
Final update: I found an old project that had an imageView set up properly so I could test Ramin's code and it works like a charm!
Final, final update:
For those of you just learning about Core Graphics, here is the simplest thing that could possibly work.
In your subclassed UIView:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColor(context, CGColorGetComponents([UIColor colorWithRed:0.5 green:0.5 blue:0 alpha:1].CGColor)); // don't make color too saturated
CGContextFillRect(context, rect); // draw base
[[UIImage imageNamed:#"someImage.png"] drawInRect: rect blendMode:kCGBlendModeOverlay alpha:1.0]; // draw image
}
In iOS7, they've introduced tintColor property on UIImageView and renderingMode on UIImage. To tint an UIImage on iOS7, all you have to do is:
UIImageView* imageView = …
UIImage* originalImage = …
UIImage* imageForRendering = [originalImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
imageView.image = imageForRendering;
imageView.tintColor = [UIColor redColor]; // or any color you want to tint it with
First you'll want to subclass UIImageView and override the drawRect method. Your class needs a UIColor property (let's call it overlayColor) to hold the blend color and a custom setter that forces a redraw when the color changes. Something like this:
- (void) setOverlayColor:(UIColor *)newColor {
if (overlayColor)
[overlayColor release];
overlayColor = [newColor retain];
[self setNeedsDisplay]; // fires off drawRect each time color changes
}
In the drawRect method you'll want to draw the image first then overlay it with a rectangle filled with the color you want along with the proper blending mode, something like this:
- (void) drawRect:(CGRect)area
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
// Draw picture first
//
CGContextDrawImage(context, self.frame, self.image.CGImage);
// Blend mode could be any of CGBlendMode values. Now draw filled rectangle
// over top of image.
//
CGContextSetBlendMode (context, kCGBlendModeMultiply);
CGContextSetFillColor(context, CGColorGetComponents(self.overlayColor.CGColor));
CGContextFillRect (context, self.bounds);
CGContextRestoreGState(context);
}
Ordinarily to optimize the drawing you would restrict the actual drawing to only the area passed in to drawRect, but since the background image has to be redrawn each time the color changes it's likely the whole thing will need refreshing.
To use it create an instance of the object then set the image property (inherited from UIImageView) to the picture and overlayColor to a UIColor value (the blend levels can be adjusted by changing the alpha value of the color you pass down).
I wanted to tint an image with alpha and I created the following class. Please let me know if you find any problems with it.
I have named my class CSTintedImageView and it inherits from UIView since UIImageView does not call the drawRect: method, like mentioned in previous replies.
I have set a designated initializer similar to the one found in the UIImageView class.
Usage:
CSTintedImageView * imageView = [[CSTintedImageView alloc] initWithImage:[UIImage imageNamed:#"image"]];
imageView.tintColor = [UIColor redColor];
CSTintedImageView.h
#interface CSTintedImageView : UIView
#property (strong, nonatomic) UIImage * image;
#property (strong, nonatomic) UIColor * tintColor;
- (id)initWithImage:(UIImage *)image;
#end
CSTintedImageView.m
#import "CSTintedImageView.h"
#implementation CSTintedImageView
#synthesize image=_image;
#synthesize tintColor=_tintColor;
- (id)initWithImage:(UIImage *)image
{
self = [super initWithFrame:CGRectMake(0, 0, image.size.width, image.size.height)];
if(self)
{
self.image = image;
//set the view to opaque
self.opaque = NO;
}
return self;
}
- (void)setTintColor:(UIColor *)color
{
_tintColor = color;
//update every time the tint color is set
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
//resolve CG/iOS coordinate mismatch
CGContextScaleCTM(context, 1, -1);
CGContextTranslateCTM(context, 0, -rect.size.height);
//set the clipping area to the image
CGContextClipToMask(context, rect, _image.CGImage);
//set the fill color
CGContextSetFillColor(context, CGColorGetComponents(_tintColor.CGColor));
CGContextFillRect(context, rect);
//blend mode overlay
CGContextSetBlendMode(context, kCGBlendModeOverlay);
//draw the image
CGContextDrawImage(context, rect, _image.CGImage);
}
#end
Just a quick clarification (after some research on this topic). The Apple doc here clearly states that:
The UIImageView class is optimized to draw its images to the display. UIImageView does not call the drawRect: method of its subclasses. If your subclass needs to include custom drawing code, you should subclass the UIView class instead.
so don't even waste any time attempting to override that method in a UIImageView subclass. Start with UIView instead.
This could be very useful: PhotoshopFramework is one powerful library to manipulate images on Objective-C. This was developed to bring the same functionalities that Adobe Photoshop users are familiar. Examples: Set colors using RGB 0-255, apply blend filers, transformations...
Is open source, here is the project link: https://sourceforge.net/projects/photoshopframew/
UIImage * image = mySourceImage;
UIColor * color = [UIColor yellowColor];
UIGraphicsBeginImageContext(image.size);
[image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height) blendMode:kCGBlendModeNormal alpha:1];
UIBezierPath * path = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, image.size.width, image.size.height)];
[color setFill];
[path fillWithBlendMode:kCGBlendModeMultiply alpha:1]; //look up blending modes for your needs
UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//use newImage for something
For those of you who try to subclass an UIImageView class and get stuck at "drawRect: is not being called", note that you should subclass an UIView class instead, because for UIImageView classes, the "drawRect:" method is not called. Read more here: drawRect not being called in my subclass of UIImageView
Here is another way to implement image tinting, especially if you are already using QuartzCore for something else. This was my answer for a similar question.
Import QuartzCore:
#import <QuartzCore/QuartzCore.h>
Create transparent CALayer and add it as a sublayer for the image you want to tint:
CALayer *sublayer = [CALayer layer];
[sublayer setBackgroundColor:[UIColor whiteColor].CGColor];
[sublayer setOpacity:0.3];
[sublayer setFrame:toBeTintedImage.frame];
[toBeTintedImage.layer addSublayer:sublayer];
Add QuartzCore to your projects Framework list (if it isn't already there), otherwise you'll get compiler errors like this:
Undefined symbols for architecture i386: "_OBJC_CLASS_$_CALayer"
The only thing I can think of would be to create a rectangular mostly transparent view with the desired color and lay it over your image view by adding it as a subview. I'm not sure if this will really tint the image in the way you imagine though, I'm not sure how you would hack into an image and selectively replace certain colors with others... sounds pretty ambitious to me.
For example:
UIImageView *yourPicture = (however you grab the image);
UIView *colorBlock = [[UIView alloc] initWithFrame:yourPicture.frame];
//Replace R G B and A with values from 0 - 1 based on your color and transparency
colorBlock.backgroundColor = [UIColor colorWithRed:R green:G blue:B alpha:A];
[yourPicture addSubView:colorBlock];
Documentation for UIColor:
colorWithRed:green:blue:alpha:
Creates and returns a color object using the specified opacity and RGB component values.
+ (UIColor *)colorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha
Parameters
red - The red component of the color object, specified as a value from 0.0 to 1.0.
green - The green component of the color object, specified as a value from 0.0 to 1.0.
blue - The blue component of the color object, specified as a value from 0.0 to 1.0.
alpha - The opacity value of the color object, specified as a value from 0.0 to 1.0.
Return Value
The color object. The color information represented by this object is in the device RGB colorspace.
Also you might want to consider caching the composited image for performance and just rendering it in drawRect:, then updated it if a dirty flag is indeed dirty. While you might be changing it often, there may be cases where draws are coming in and you're not dirty, so you can simply refresh from the cache. If memory is more of an issue than performance, you can ignore this :)
I have a library I open-sourced for this: ios-image-filters
For Swift 2.0,
let image: UIImage! = UIGraphicsGetImageFromCurrentImageContext()
imgView.image = imgView.image!.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate)
imgView.tintColor = UIColor(red: 51/255.0, green: 51/255.0, blue:
51/255.0, alpha: 1.0)
I made macros for this purpose:
#define removeTint(view) \
if ([((NSNumber *)[view.layer valueForKey:#"__hasTint"]) boolValue]) {\
for (CALayer *layer in [view.layer sublayers]) {\
if ([((NSNumber *)[layer valueForKey:#"__isTintLayer"]) boolValue]) {\
[layer removeFromSuperlayer];\
break;\
}\
}\
}
#define setTint(view, tintColor) \
{\
if ([((NSNumber *)[view.layer valueForKey:#"__hasTint"]) boolValue]) {\
removeTint(view);\
}\
[view.layer setValue:#(YES) forKey:#"__hasTint"];\
CALayer *tintLayer = [CALayer new];\
tintLayer.frame = view.bounds;\
tintLayer.backgroundColor = [tintColor CGColor];\
[tintLayer setValue:#(YES) forKey:#"__isTintLayer"];\
[view.layer addSublayer:tintLayer];\
}
To use, simply just call:
setTint(yourView, yourUIColor);
//Note: include opacity of tint in your UIColor using the alpha channel (RGBA), e.g. [UIColor colorWithRed:0.5f green:0.0 blue:0.0 alpha:0.25f];
When removing the tint simply call:
removeTint(yourView);
Related
I am using FSCalendar What I want to do is change the way of showing events. I want to put a coloured rectangle border around the cell with events. I did that by editing the cell background layer and worked fine, but now I realized that it is the wrong place to put my code as updating to the latest version of FSCalendar which will override my changes.
One of what I can access by the calendar delegates is to set image to the cell, So I want to create image as rectange border with the event colour.
Here is an image of what I want:
Any suggessions is appreicated.
Thanks in advance.
This method will draw a bordered rectangle. I would make it class method and put into UIImage category for convenient use.
- (UIImage *)imageWithColor:(UIColor *)color size:(CGSize)imageSize andBorderWidth:(CGFloat)borderWidth fillWithColor:(BOOL)fillWithColor{
UIGraphicsBeginImageContext(imageSize);
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect rect = CGRectMake(0, 0, imageSize.width, imageSize.height);
if(fillWithColor) {
[color setFill];
CGContextFillRect(context, rect);
} else {
[color setStroke];
CGContextStrokeRectWithWidth(context, rect, borderWidth);
}
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
EDIT: Added fillWithColor parameter
You can set border for UIImageView instead of set image as rectange border
cell.imageView.layer.borderColor = [[UIColor redColor] CGColor];
cell.imageView.layer.borderWidth = 1.0f;
I want to draw text onto my subclass on UIView so that the text is cut out of the shape and the background behind the view shows through, just like in the OSX Mavericks logo found here.
I would say that I'm more of an intermediate/early advanced iOS developer so feel free to throw some crazy solutions at me. I'd expect I'd have to override drawRect in order to do this.
Thanks guys!
EDIT:
I should mention that my first attempt was making the text [UIColor clearColor] which didn't work since that just set the alpha component of the text to 0, which just showed the view through the text.
Disclaimer: I'm writing this without testing, so forgive me if I'm wrong here.
You should achieve what you need by these two steps:
Create a CATextLayer with the size of your view, set the backgroundColor to be fully transparent and foregroundColor to be opaque (by [UIColor colorWithWhite:0 alpha:1] and [UIColor colorWithWhite:0 alpha:0]. Then set the string property to the string you want to render, font and fontSize etc.
Set your view's layer's mask to this layer: myView.layer.mask = textLayer. You'll have to import QuartzCore to access the CALayer of your view.
Note that it's possible that I switched between the opaque and transparent color in the first step.
Edit: Indeed, Noah was right. To overcome this, I used CoreGraphics with the kCGBlendModeDestinationOut blend mode.
First, a sample view that shows that it indeed works:
#implementation TestView
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor clearColor];
}
return self;
}
- (void)drawRect:(CGRect)rect {
[[UIColor redColor] setFill];
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:10];
[path fill];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context); {
CGContextSetBlendMode(context, kCGBlendModeDestinationOut);
[#"Hello!" drawAtPoint:CGPointZero withFont:[UIFont systemFontOfSize:24]];
} CGContextRestoreGState(context);
}
#end
After adding this to your view controller, you'll see the view behind TestView where Hello! is drawn.
Why does this work:
The blend mode is defined as R = D*(1 - Sa), meaning we need opposite alpha values than in the mask layer I suggested earlier. Therefore, all you need to do is to draw with an opaque color and this will be subtracted from the stuff you've drawn on the view beforehand.
If all you want is a white view with some stuff (text images, etc) cut out, then you can just do
yourView.layer.compositingFilter = "screenBlendMode"
This will leave the white parts white and the black parts will be see-through.
I actually figured out how to do it on my own surprisingly but #StatusReport's answer is completely valid and works as it stands now.
Here's how I did it:
-(void)drawRect:(CGRect)rect{
CGContextRef context = UIGraphicsGetCurrentContext();
[[UIColor darkGrayColor]setFill]; //this becomes the color of the alpha mask
CGContextFillRect(context, rect);
CGContextSaveState(context);
[[UIColor whiteColor]setFill];
//Have to flip the context since core graphics is done in cartesian coordinates
CGContextTranslateCTM(context, 0, rect.size.height);
CGContextScaleCTM(context, 1.0, -1.0);
[textToDraw drawInRect:rect withFont:[UIFont fontWithName:#"HelveticaNeue-Thin" size:40];
CGContextRestoreGState(context);
CGImageRef alphaMask = CGBitmapContextCreateImage(context);
[[UIColor whiteColor]setFill];
CGContextFillRect(context, rect);
CGContextSaveGState(context);
CGContentClipToMask(context, rect, alphaMask);
[backgroundImage drawInRect:rect];
CGContextRestoreGState(context);
CGImageRelease(alphaMask);
}
- (void)setTextToDraw:(NSString*)text{
if(text != textToDraw){
textToDraw = text;
[self setNeedsDisplay];
}
}
I have an #synthesize declaration for textToDraw so I could override the setter and call [self setNeedsDisplay]. Not sure if that's totally necessary or not.
I'm sure this has some typos but I can assure you, the spell checked version runs just fine.
StatusReport's accepted answer is beautifully written and because I have yet to find a Swift answer to this question, I thought I'd use his answer as the template for the Swift version. I added a little extensibility to the view by allowing the input of the string as a parameter of the view to remind people that that his can be done with all of the view's properties (corner radius, color, etc.) to make the view completely extensible.
The frame is zeroed out in the initializer because you're most likely going to apply constraints. If you aren't using constraints, omit this.
Swift 5
class CustomView: UIView {
var title: String
init(title: String) {
self.title = title
super.init(frame: CGRect.zero)
config()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func config() {
backgroundColor = UIColor.clear
}
override func draw(_ rect: CGRect) { // do not call super
UIColor.red.setFill()
let path = UIBezierPath(roundedRect: bounds, cornerRadius: 10)
path.fill()
weak var context = UIGraphicsGetCurrentContext() // Apple wants this to be weak
context?.saveGState()
context?.setBlendMode(CGBlendMode.destinationOut)
title.draw(at: CGPoint.zero, withAttributes: [NSAttributedString.Key.font : UIFont.systemFont(ofSize: 24)])
context?.restoreGState()
}
}
let customView = CustomView(title: "great success")
My goal is to add gradients for my uilabel by doing the following (CustomLabelBackGround is subclass of UILabel)
#implementation CustomLabelBackGround
- (id)initWithFrame:(CGRect)frame {
if ((self = [super initWithFrame:frame])) {
// Initialization code
}
return self;
}
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGColorRef whiteColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0].CGColor;
CGColorRef lightGrayColor = [UIColor colorWithRed:230.0/255.0 green:230.0/255.0 blue:230.0/255.0 alpha:1.0].CGColor;
CGColorRef separatorColor = [UIColor colorWithRed:208.0/255.0 green:208.0/255.0 blue:208.0/255.0 alpha:1.0].CGColor;
CGRect paperRect = self.bounds;
// Fill with gradient
drawLinearGradient(context, paperRect, whiteColor, lightGrayColor);
// Add white 1 px stroke
CGRect strokeRect = paperRect;
strokeRect.size.height -= 1;
strokeRect = rectFor1PxStroke(strokeRect);
CGContextSetStrokeColorWithColor(context, whiteColor);
CGContextSetLineWidth(context, 1.0);
CGContextStrokeRect(context, strokeRect);
// Add separator
CGPoint startPoint = CGPointMake(paperRect.origin.x, paperRect.origin.y + paperRect.size.height - 1);
CGPoint endPoint = CGPointMake(paperRect.origin.x + paperRect.size.width - 1, paperRect.origin.y + paperRect.size.height - 1);
draw1PxStroke(context, startPoint, endPoint, separatorColor);
}
However, the text of CustomLabelBackGround is disappearing when I am trying to display onto the screen. please look at the picture below as reference:
What I am missing here. Please help if you have any ideas about this. thanks
You need to call :
[super drawRect:rect];
in order to super draw your text before drawing your anything else. The text with then be drawn normaly
Since you are implementing drawRect for UILabel you are owning that Graphics Context, therefore you are basically "overriding" the text, you can draw that yourself as well, or an easier approach would just be to have a container view with the gradient and a transparent label with the text.
The best approach
Create a custom UIView
make this drawrect implement there
In this UILabel class add that class as background to the label
Otherwise you have to draw the label contents also since you are overriding its drawrect method
Why not just pre-render the gradient in photoshop and then on your label just set:
myLabel.backgroundColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"myImage"]];
I would:
Create a custom UIView,and use your drawRect code there.
Make sure that the UILabel background is set to [UIColor
clearColor].
Add the Custom UILabel Class as a
subview of the Custom UIView class.
Or just do like Imram said, and create that gradient as an image in Photoshop or some other graphic editor program, and set the label background there. Personally, I think this may be the simplest and most maintainable solution.
I love the new grouped background color of my tableView on ipad. I would like to use that same color on the background of my UIViewController which is on the right side of my split controller.
Does anyone know what this color is? It seems to be a slight gradient.
Based on Lutz's answer, and the answer to this question, the following custom view controller code creates a replica of the table view's background view. There is, however, a problem with auto-rotation, which is being addressed in the second code snippet below.
// You also need to link against QuartzCore.framework
#import <QuartzCore/QuartzCore.h>
- (void) loadView
{
CGRect mainViewFrame = [self mainViewFrame];
self.view = [[[UIView alloc] initWithFrame:mainViewFrame] autorelease];
CAGradientLayer* gradient = [CAGradientLayer layer];
gradient.frame = self.view.bounds;
UIColor* startColor = [UIColor colorWithRed:226.0/255.0 green:229.0/255.0 blue:234.0/255.0 alpha:1.0];
UIColor* endColor = [UIColor colorWithRed:208.0/255.0 green:210.0/255.0 blue:216.0/255.0 alpha:1.0];
// Cast to (id) is necessary to get rid of a compiler warning
gradient.colors = [NSArray arrayWithObjects:(id)startColor.CGColor, (id)endColor.CGColor, nil];
// Inserting at index position 0 ensures that the gradient is drawn
// in the background even if the view already has subviews or other
// sublayers
[view.layer insertSublayer:gradient atIndex:0];
// add more subviews
}
- (CGRect) mainViewFrame
{
// add your frame calculating code here
}
- (void) willAnimateRotationToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation duration:(NSTimeInterval)duration
{
[UIView animateWithDuration:duration
delay:0
options:UIViewAnimationCurveLinear
animations:^{
((CALayer*)[self.view.layer.sublayers objectAtIndex:0]).frame = self.view.bounds;
}
completion:NULL];
}
The problem with the above code is that, while the rotation animation is running, the original white background is visible for a very short time. Unfortunately I don't understand enough about layers to fix this, so I started to look for an alternative to CAGradientLayer. Setting CALayer.contents with a gradient image is what I found.
Most of the code below deals with creating the pattern image required as input for the convenience constructor, only this time the gradient is drawn "manually" with Core Graphics instead of with a CAGradientLayer. The gradient drawing code, by the way, is heavily based on Ray Wenderlich's Core Graphics 101 tutorial.
#import <QuartzCore/QuartzCore.h>
- (void) loadView
{
CGRect mainViewFrame = [self mainViewFrame];
self.view = [[[UIView alloc] initWithFrame:mainViewFrame] autorelease];
UIColor* startColor = [UIColor colorWithRed:226.0/255.0 green:229.0/255.0 blue:234.0/255.0 alpha:1.0];
UIColor* endColor = [UIColor colorWithRed:208.0/255.0 green:210.0/255.0 blue:216.0/255.0 alpha:1.0];
UIImage* backgroundPattern = [self gradientImageWithSize:CGSizeMake(1, mainViewFrame.size.height)
startColor:startColor
endColor:endColor];
self.view.layer.contents = (id)backgroundPattern.CGImage;
// add more subviews
}
- (CGRect) mainViewFrame
{
// add your frame calculating code here
}
- (UIImage*) gradientImageWithSize:(CGSize)size startColor:(UIColor*)startColor endColor:(UIColor*)endColor
{
UIGraphicsBeginImageContext(size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect rect = CGRectMake(0, 0, size.width, size.height);
[self drawLinearGradientWithContext:context rect:rect startColor:startColor.CGColor endColor:endColor.CGColor];
UIImage* gradientImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return gradientImage;
}
- (void) drawLinearGradientWithContext:(CGContextRef)context rect:(CGRect)rect startColor:(CGColorRef)startColor endColor:(CGColorRef)endColor
{
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat locations[] = { 0.0, 1.0 };
NSArray* colors = [NSArray arrayWithObjects:(id)startColor, (id)endColor, nil];
// NSArray is toll-free bridged, so we can simply cast to CGArrayRef
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace,
(CFArrayRef)colors,
locations);
// Draw the gradient from top-middle to bottom-middle
CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
// Remember context so that later on we can undo the clipping we are going to
// add to the Core Graphics state machine
CGContextSaveGState(context);
// Add clipping with the specified rect so that we can simply draw into the
// specified context without changing anything outside of the rect. With this
// approach, the caller can give us a context that already has other stuff
// in it
CGContextAddRect(context, rect);
CGContextClip(context);
// Finally draw the gradient
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
// Undo clipping
CGContextRestoreGState(context);
// Cleanup memory allocated by CGContextDrawLinearGradient()
CGGradientRelease(gradient);
// Cleanup memory allocated by CGColorSpaceCreateDeviceRGB()
CGColorSpaceRelease(colorSpace);
}
I like this code best because
It auto-rotates cleanly
There is no custom code to handle auto-rotation
The gradient-related functions can be refactored into a separate utility class to make them more re-usable
It is not a color but a UIImageView with a gradient image:
UIImageView* imageView = (UIImageView*) self.tableView.backgroundView;
NSString* file = [NSHomeDirectory() stringByAppendingPathComponent:#"Documents/Test.png"];
NSData* data = UIImagePNGRepresentation(imageView.image);
[data writeToFile:file atomically:YES];
[UIColor colorWithRed:0.953 green:0.953 blue:0.953 alpha:1] /#f3f3f3/
I'm just throwing it here, but if you could use SVG somewhere (like in a web page, or in an app using some kind of SVG-to-Core-Graphics library, I know a few exist, but it's not the ideal solution), I've got the code:
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">
<linearGradient id="g146" gradientUnits="userSpaceOnUse" x1="0%" y1="0%" x2="0%" y2="100%">
<stop stop-color="#E2E5EA" offset="0" />
<stop stop-color="#D1D3D9" offset="1" />
</linearGradient>
<rect x="0" y="0" width="1" height="1" fill="url(#g146)" />
</svg>
Created using the DigitalColor Meter app (IMO an awful name for an Apple app), Xcode 4.5ß4 and SVG Gradient Background Maker.
These are all the out of the box texture colors, static methods on UIColor:
+ (UIColor *)lightTextColor; // for a dark background
+ (UIColor *)darkTextColor; // for a light background
+ (UIColor *)groupTableViewBackgroundColor;
+ (UIColor *)viewFlipsideBackgroundColor;
+ (UIColor *)scrollViewTexturedBackgroundColor __OSX_AVAILABLE_STARTING(__MAC_NA, __IPHONE_3_2);
I would like to tint an image with a color reference. The results should look like the Multiply blending mode in Photoshop, where whites would be replaced with tint:
I will be changing the color value continuously.
Follow up: I would put the code to do this in my ImageView's drawRect: method, right?
As always, a code snippet would greatly aid in my understanding, as opposed to a link.
Update: Subclassing a UIImageView with the code Ramin suggested.
I put this in viewDidLoad: of my view controller:
[self.lena setImage:[UIImage imageNamed:kImageName]];
[self.lena setOverlayColor:[UIColor blueColor]];
[super viewDidLoad];
I see the image, but it is not being tinted. I also tried loading other images, setting the image in IB, and calling setNeedsDisplay: in my view controller.
Update: drawRect: is not being called.
Final update: I found an old project that had an imageView set up properly so I could test Ramin's code and it works like a charm!
Final, final update:
For those of you just learning about Core Graphics, here is the simplest thing that could possibly work.
In your subclassed UIView:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColor(context, CGColorGetComponents([UIColor colorWithRed:0.5 green:0.5 blue:0 alpha:1].CGColor)); // don't make color too saturated
CGContextFillRect(context, rect); // draw base
[[UIImage imageNamed:#"someImage.png"] drawInRect: rect blendMode:kCGBlendModeOverlay alpha:1.0]; // draw image
}
In iOS7, they've introduced tintColor property on UIImageView and renderingMode on UIImage. To tint an UIImage on iOS7, all you have to do is:
UIImageView* imageView = …
UIImage* originalImage = …
UIImage* imageForRendering = [originalImage imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate];
imageView.image = imageForRendering;
imageView.tintColor = [UIColor redColor]; // or any color you want to tint it with
First you'll want to subclass UIImageView and override the drawRect method. Your class needs a UIColor property (let's call it overlayColor) to hold the blend color and a custom setter that forces a redraw when the color changes. Something like this:
- (void) setOverlayColor:(UIColor *)newColor {
if (overlayColor)
[overlayColor release];
overlayColor = [newColor retain];
[self setNeedsDisplay]; // fires off drawRect each time color changes
}
In the drawRect method you'll want to draw the image first then overlay it with a rectangle filled with the color you want along with the proper blending mode, something like this:
- (void) drawRect:(CGRect)area
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
// Draw picture first
//
CGContextDrawImage(context, self.frame, self.image.CGImage);
// Blend mode could be any of CGBlendMode values. Now draw filled rectangle
// over top of image.
//
CGContextSetBlendMode (context, kCGBlendModeMultiply);
CGContextSetFillColor(context, CGColorGetComponents(self.overlayColor.CGColor));
CGContextFillRect (context, self.bounds);
CGContextRestoreGState(context);
}
Ordinarily to optimize the drawing you would restrict the actual drawing to only the area passed in to drawRect, but since the background image has to be redrawn each time the color changes it's likely the whole thing will need refreshing.
To use it create an instance of the object then set the image property (inherited from UIImageView) to the picture and overlayColor to a UIColor value (the blend levels can be adjusted by changing the alpha value of the color you pass down).
I wanted to tint an image with alpha and I created the following class. Please let me know if you find any problems with it.
I have named my class CSTintedImageView and it inherits from UIView since UIImageView does not call the drawRect: method, like mentioned in previous replies.
I have set a designated initializer similar to the one found in the UIImageView class.
Usage:
CSTintedImageView * imageView = [[CSTintedImageView alloc] initWithImage:[UIImage imageNamed:#"image"]];
imageView.tintColor = [UIColor redColor];
CSTintedImageView.h
#interface CSTintedImageView : UIView
#property (strong, nonatomic) UIImage * image;
#property (strong, nonatomic) UIColor * tintColor;
- (id)initWithImage:(UIImage *)image;
#end
CSTintedImageView.m
#import "CSTintedImageView.h"
#implementation CSTintedImageView
#synthesize image=_image;
#synthesize tintColor=_tintColor;
- (id)initWithImage:(UIImage *)image
{
self = [super initWithFrame:CGRectMake(0, 0, image.size.width, image.size.height)];
if(self)
{
self.image = image;
//set the view to opaque
self.opaque = NO;
}
return self;
}
- (void)setTintColor:(UIColor *)color
{
_tintColor = color;
//update every time the tint color is set
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
//resolve CG/iOS coordinate mismatch
CGContextScaleCTM(context, 1, -1);
CGContextTranslateCTM(context, 0, -rect.size.height);
//set the clipping area to the image
CGContextClipToMask(context, rect, _image.CGImage);
//set the fill color
CGContextSetFillColor(context, CGColorGetComponents(_tintColor.CGColor));
CGContextFillRect(context, rect);
//blend mode overlay
CGContextSetBlendMode(context, kCGBlendModeOverlay);
//draw the image
CGContextDrawImage(context, rect, _image.CGImage);
}
#end
Just a quick clarification (after some research on this topic). The Apple doc here clearly states that:
The UIImageView class is optimized to draw its images to the display. UIImageView does not call the drawRect: method of its subclasses. If your subclass needs to include custom drawing code, you should subclass the UIView class instead.
so don't even waste any time attempting to override that method in a UIImageView subclass. Start with UIView instead.
This could be very useful: PhotoshopFramework is one powerful library to manipulate images on Objective-C. This was developed to bring the same functionalities that Adobe Photoshop users are familiar. Examples: Set colors using RGB 0-255, apply blend filers, transformations...
Is open source, here is the project link: https://sourceforge.net/projects/photoshopframew/
UIImage * image = mySourceImage;
UIColor * color = [UIColor yellowColor];
UIGraphicsBeginImageContext(image.size);
[image drawInRect:CGRectMake(0, 0, image.size.width, image.size.height) blendMode:kCGBlendModeNormal alpha:1];
UIBezierPath * path = [UIBezierPath bezierPathWithRect:CGRectMake(0, 0, image.size.width, image.size.height)];
[color setFill];
[path fillWithBlendMode:kCGBlendModeMultiply alpha:1]; //look up blending modes for your needs
UIImage * newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
//use newImage for something
For those of you who try to subclass an UIImageView class and get stuck at "drawRect: is not being called", note that you should subclass an UIView class instead, because for UIImageView classes, the "drawRect:" method is not called. Read more here: drawRect not being called in my subclass of UIImageView
Here is another way to implement image tinting, especially if you are already using QuartzCore for something else. This was my answer for a similar question.
Import QuartzCore:
#import <QuartzCore/QuartzCore.h>
Create transparent CALayer and add it as a sublayer for the image you want to tint:
CALayer *sublayer = [CALayer layer];
[sublayer setBackgroundColor:[UIColor whiteColor].CGColor];
[sublayer setOpacity:0.3];
[sublayer setFrame:toBeTintedImage.frame];
[toBeTintedImage.layer addSublayer:sublayer];
Add QuartzCore to your projects Framework list (if it isn't already there), otherwise you'll get compiler errors like this:
Undefined symbols for architecture i386: "_OBJC_CLASS_$_CALayer"
The only thing I can think of would be to create a rectangular mostly transparent view with the desired color and lay it over your image view by adding it as a subview. I'm not sure if this will really tint the image in the way you imagine though, I'm not sure how you would hack into an image and selectively replace certain colors with others... sounds pretty ambitious to me.
For example:
UIImageView *yourPicture = (however you grab the image);
UIView *colorBlock = [[UIView alloc] initWithFrame:yourPicture.frame];
//Replace R G B and A with values from 0 - 1 based on your color and transparency
colorBlock.backgroundColor = [UIColor colorWithRed:R green:G blue:B alpha:A];
[yourPicture addSubView:colorBlock];
Documentation for UIColor:
colorWithRed:green:blue:alpha:
Creates and returns a color object using the specified opacity and RGB component values.
+ (UIColor *)colorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue alpha:(CGFloat)alpha
Parameters
red - The red component of the color object, specified as a value from 0.0 to 1.0.
green - The green component of the color object, specified as a value from 0.0 to 1.0.
blue - The blue component of the color object, specified as a value from 0.0 to 1.0.
alpha - The opacity value of the color object, specified as a value from 0.0 to 1.0.
Return Value
The color object. The color information represented by this object is in the device RGB colorspace.
Also you might want to consider caching the composited image for performance and just rendering it in drawRect:, then updated it if a dirty flag is indeed dirty. While you might be changing it often, there may be cases where draws are coming in and you're not dirty, so you can simply refresh from the cache. If memory is more of an issue than performance, you can ignore this :)
I have a library I open-sourced for this: ios-image-filters
For Swift 2.0,
let image: UIImage! = UIGraphicsGetImageFromCurrentImageContext()
imgView.image = imgView.image!.imageWithRenderingMode(UIImageRenderingMode.AlwaysTemplate)
imgView.tintColor = UIColor(red: 51/255.0, green: 51/255.0, blue:
51/255.0, alpha: 1.0)
I made macros for this purpose:
#define removeTint(view) \
if ([((NSNumber *)[view.layer valueForKey:#"__hasTint"]) boolValue]) {\
for (CALayer *layer in [view.layer sublayers]) {\
if ([((NSNumber *)[layer valueForKey:#"__isTintLayer"]) boolValue]) {\
[layer removeFromSuperlayer];\
break;\
}\
}\
}
#define setTint(view, tintColor) \
{\
if ([((NSNumber *)[view.layer valueForKey:#"__hasTint"]) boolValue]) {\
removeTint(view);\
}\
[view.layer setValue:#(YES) forKey:#"__hasTint"];\
CALayer *tintLayer = [CALayer new];\
tintLayer.frame = view.bounds;\
tintLayer.backgroundColor = [tintColor CGColor];\
[tintLayer setValue:#(YES) forKey:#"__isTintLayer"];\
[view.layer addSublayer:tintLayer];\
}
To use, simply just call:
setTint(yourView, yourUIColor);
//Note: include opacity of tint in your UIColor using the alpha channel (RGBA), e.g. [UIColor colorWithRed:0.5f green:0.0 blue:0.0 alpha:0.25f];
When removing the tint simply call:
removeTint(yourView);