I would like to create a CGLayer, contains a point with gradient in boundary. Because I will use this layer to draw multiple points on the main screen.
This is my code:
CGRect r = CGRectMake(0, 0, 64, 64);
textureLayer = CGLayerCreateWithContext(context, r.size, NULL);
CGContextRef textureContext = CGLayerGetContext(textureLayer);
CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
CGFloat colors[] =
{
0.0, 0.0, 0.0, 1.0,
1.0, 1.0, 1.0, 1.0
};
gradient = CGGradientCreateWithColorComponents(rgb, colors, NULL, sizeof(colors)/(sizeof(colors[0])*4));
CGColorSpaceRelease(rgb);
CGPoint start, end;
start = end = CGPointMake(32, 32);
CGFloat startRadius = 20;
CGFloat endRadius = 30;
CGContextSaveGState(textureContext);
CGGradientDrawingOptions options = 0 | kCGGradientDrawsBeforeStartLocation | kCGGradientDrawsAfterEndLocation;
CGContextDrawRadialGradient(textureContext, gradient, start, startRadius, end, endRadius, options);
CGContextRestoreGState(textureContext);
I got this result:
It is correct. But when I draw on main screen, not the stroke like I expect, this is the result:
I think the problem is the white background in my CGLayer. How I can make that background be transparent?
Thank for your help.
I need to draw a box filled with a gradient. I'm using a UIView and overwriting the -drawRect method.
Here is my code (simplified):
CGContextRef c = UIGraphicsGetCurrentContext();
CGFloat components[8] = {158.0/255.0,36.0/255.0,134.0/255.0,1.0,115.0/255.0,26.0/255.0,93.0/255.0,1.0};
CGColorSpaceRef rgbColorspace = CGColorSpaceCreateDeviceRGB();
CGFloat locations[2] = {0.0,1.0};
CGGradientRef glossGradient = CGGradientCreateWithColorComponents(rgbColorspace, components, locations, 2);
CGPoint topCenter = CGPointMake(10, 0);
CGPoint bottomCenter = CGPointMake(10, 10);
CGContextDrawLinearGradient(c, glossGradient, topCenter, bottomCenter, 0);
The gradient fill at present fills the entire width of the view! What can I do to control the width of the gradient-filled box I'm drawing?
Nevermind.. Found the answer. I need to clip my CGContextRef to match the CGRect I wanted:
CGContextClipToRect(c, currentBounds);
I am using following code for Drawing Arc in DrawRect...I want to draw Arc only with gradient effect ...any help please?
CGContextSetAlpha(ctx, 0.5);
CGContextSetRGBFillColor(ctx, color.red, color.green, color.blue, color.alpha );
CGContextMoveToPoint(ctx, cX, cY);
CGContextAddArc(ctx, cX, cY, radious+10, (startDeg-90)*M_PI/180.0, (endDeg-90)*M_PI/180.0, 0);
CGContextClosePath(ctx);
CGContextFillPath(ctx);
First create an arc then use the following code to apply gradient effect to the path,
CGGradientRef glossGradient;
CGColorSpaceRef rgbColorspace;
size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 };
CGFloat components[8] = { 0.965, 0.965, 0.965, 1.0, // Start color
0.603, 0.603, 0.603, 1.0 }; // End color
rgbColorspace = CGColorSpaceCreateDeviceRGB();
glossGradient = CGGradientCreateWithColorComponents(rgbColorspace, components, locations, num_locations);
CGRect currentBounds = self.bounds;
CGPoint topCenter = CGPointMake(CGRectGetMidX(currentBounds), 0.0f);
CGPoint midCenter = CGPointMake(CGRectGetMidX(currentBounds), CGRectGetMaxY(currentBounds));
CGContextDrawLinearGradient(UIGraphicsGetCurrentContext(), glossGradient, topCenter, midCenter, 0);
CGGradientRelease(glossGradient);
CGColorSpaceRelease(rgbColorspace);
EDIT:
I finally found a real simple solution to this problem, using the CAGradientLayer class, and the CALayer drawing functionalities.
Ole Begemann released a great UIView wrapper for CAGradientLayer class named OBGradientView.
This class allows you to easily create a gradient UIView in your application.
You then use the CALayer drawing functionalities to add the rounded corners and drop shadow values :
// Create the gradient view
OBGradientView *gradient = [[OBGradientView alloc] initWithFrame:someRect];
NSArray *colors = [NSArray arrayWithObjects:[UIColor redColor], [UIColor yellowColor], nil];
gradient.colors = colors;
// Set rounded corners and drop shadow
gradient.layer.cornerRadius = 5.0;
gradient.layer.shadowColor = [UIColor grayColor].CGColor;
gradient.layer.shadowOpacity = 1.0;
gradient.layer.shadowOffset = CGSizeMake(2.0, 2.0);
gradient.layer.shadowRadius = 3.0;
[self.view addSubview:gradient];
[gradient release];
Dont forget to add the QuartzCore framework to your project.
ORIGINAL QUESTION:
I have been working on a custom control that is a rounded rectangle button, filled with a linear gradient, and having a drop shadow.
I have filled the two first steps using this answer : link text
My problem is now to add a drop shadow under the resulting shape.
Actually, the context has been clipped to the rounded rect path, so when I use the CGContextSetShadow function, it doesn't draw it.
I tried to solve this problem by drawing the rounded rect twice, first with a plain color, so it draws the shadow, and then redraw it with the gradient fill.
It kinda worked, but I still can see a few pixels at the corners of the shape resulting from the first draw with a plain color, as you can see on this zoomed version :
http://img269.imageshack.us/img269/6489/capturedcran20100701192.png
It is almost good, but not perfect yet...
Here is my -drawRect: implementation :
static void addRoundedRectToPath(CGContextRef context, CGRect rect, float ovalWidth, float ovalHeight)
{
float fw, fh;
if (ovalWidth == 0 || ovalHeight == 0) {
CGContextAddRect(context, rect);
return;
}
CGContextSaveGState(context);
CGContextTranslateCTM (context, CGRectGetMinX(rect), CGRectGetMinY(rect));
CGContextScaleCTM (context, ovalWidth, ovalHeight);
fw = CGRectGetWidth (rect) / ovalWidth;
fh = CGRectGetHeight (rect) / ovalHeight;
CGContextMoveToPoint(context, fw, fh/2);
CGContextAddArcToPoint(context, fw, fh, fw/2, fh, 1);
CGContextAddArcToPoint(context, 0, fh, 0, fh/2, 1);
CGContextAddArcToPoint(context, 0, 0, fw/2, 0, 1);
CGContextAddArcToPoint(context, fw, 0, fw, fh/2, 1);
CGContextClosePath(context);
CGContextRestoreGState(context);
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGSize shadowOffset = CGSizeMake(10.0, 10.0);
CGFloat blur = 5.0;
rect.size.width -= shadowOffset.width + blur;
rect.size.height -= shadowOffset.height + blur;
CGContextSaveGState(context);
addRoundedRectToPath(context, rect, _radius, _radius);
CGContextSetShadow (context, shadowOffset, blur);
CGContextDrawPath(context, kCGPathFill);
CGContextRestoreGState(context);
addRoundedRectToPath(context, rect, _radius, _radius);
CGContextClip(context);
CGFloat colors[] =
{
_gradientStartColor.red, _gradientStartColor.green, _gradientStartColor.blue, _gradientStartColor.alpha,
_gradientEndColor.red, _gradientEndColor.green, _gradientEndColor.blue, _gradientEndColor.alpha
};
size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 };
CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(rgb, colors, locations, num_locations);
CGRect currentBounds = self.bounds;
CGPoint gStartPoint = CGPointMake(CGRectGetMidX(currentBounds), 0.0f);
CGPoint gEndPoint = CGPointMake(CGRectGetMidX(currentBounds), CGRectGetMaxY(currentBounds));
CGContextDrawLinearGradient(context, gradient, gStartPoint, gEndPoint, 0);
CGColorSpaceRelease(rgb);
CGGradientRelease(gradient);
}
Any ideas on how to do this in another way ?
Thanks !
In order to create a rounded corner view with a gradient background and drop shadow, here's what did:
The first part is very similar to what was provided in the question, it creates a rounded rect path using CGPathAddArcToPoint as described very well in this article. Here's a picture to help me understand it:
The second part works as follows:
Enable shadowing on the graphics context, add the path that was just defined, then fill that path. You can't apply the shadow to just the path itself (paths are not part of the graphics state), so you need to fill the path in order for the shadow to appear (I suppose a stroked path might also work?). You can't simply apply the shadow to a gradient since it's not really a standard fill (see this post for more info).
Once you have a filled rounded rect that creates the shadow, you need to draw the gradient over top of that. So add the path a second time in order to set the clipping area, then draw the gradient using CGContextDrawLinearGradient. I don't think you can easily "fill" a path with a gradient like you could with the earlier standard-fill step, so instead you fill the drawing area with the gradient and then clip to the rounded rectangle area that you're interested in.
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
CGGradientRef gradient = [self normalGradient];
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGMutablePathRef outlinePath = CGPathCreateMutable();
float offset = 5.0;
float w = [self bounds].size.width;
float h = [self bounds].size.height;
CGPathMoveToPoint(outlinePath, nil, offset*2.0, offset);
CGPathAddArcToPoint(outlinePath, nil, offset, offset, offset, offset*2, offset);
CGPathAddLineToPoint(outlinePath, nil, offset, h - offset*2.0);
CGPathAddArcToPoint(outlinePath, nil, offset, h - offset, offset *2.0, h-offset, offset);
CGPathAddLineToPoint(outlinePath, nil, w - offset *2.0, h - offset);
CGPathAddArcToPoint(outlinePath, nil, w - offset, h - offset, w - offset, h - offset * 2.0, offset);
CGPathAddLineToPoint(outlinePath, nil, w - offset, offset*2.0);
CGPathAddArcToPoint(outlinePath, nil, w - offset , offset, w - offset*2.0, offset, offset);
CGPathCloseSubpath(outlinePath);
CGContextSetShadow(ctx, CGSizeMake(4,4), 3);
CGContextAddPath(ctx, outlinePath);
CGContextFillPath(ctx);
CGContextAddPath(ctx, outlinePath);
CGContextClip(ctx);
CGPoint start = CGPointMake(rect.origin.x, rect.origin.y);
CGPoint end = CGPointMake(rect.origin.x, rect.size.height);
CGContextDrawLinearGradient(ctx, gradient, start, end, 0);
CGPathRelease(outlinePath);
}
- (CGGradientRef)normalGradient
{
NSMutableArray *normalGradientLocations = [NSMutableArray arrayWithObjects:
[NSNumber numberWithFloat:0.0f],
[NSNumber numberWithFloat:1.0f],
nil];
NSMutableArray *colors = [NSMutableArray arrayWithCapacity:2];
UIColor *color = [UIColor colorWithRed:0.2745 green:0.2745 blue:0.2745 alpha:1.0];
[colors addObject:(id)[color CGColor]];
color = [UIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:1.0];
[colors addObject:(id)[color CGColor]];
NSMutableArray *normalGradientColors = colors;
int locCount = [normalGradientLocations count];
CGFloat locations[locCount];
for (int i = 0; i < [normalGradientLocations count]; i++)
{
NSNumber *location = [normalGradientLocations objectAtIndex:i];
locations[i] = [location floatValue];
}
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
CGGradientRef normalGradient = CGGradientCreateWithColors(space, (CFArrayRef)normalGradientColors, locations);
CGColorSpaceRelease(space);
return normalGradient;
}
I have solution that does not need pre-fill of the path. Advantage(?) is that the shadow can use transparency effects of the gradient (i.e. if gradient is from opaque to trasparent, shadow will be partially transparent as well) and is simpler.
It goes more or less like:
CGContextSetShadowWithColor();
CGContextBeginTransparencyLayer();
CGContextSaveGState();
CGContextClip();
CGGradientCreateWithColorComponents();
CGContextRestoreGState();
CGContextEndTransparencyLayer();
CGContextSetShadowWithColor(..., NULL);
I suppose that is beacuse CGContextBeginTransparencyLayer/CGContextEndTransparencyLayer is outside the clip and the shadow is applied to that layer (which contains gradient filled path). At least it seems to work for me.
For shadows you can use CGContextSetShadow()
This code will draw something with a shadow:
- (void)drawTheRealThingInContext:(CGContextRef)ctx
{
// calculate x, y, w, h and inset here...
CGContextMoveToPoint(ctx, x+inset, y);
CGContextAddLineToPoint(ctx, x+w-inset, y);
CGContextAddArcToPoint(ctx, x+w, y, x+w, y+inset, inset);
CGContextAddLineToPoint(ctx, x+w, y+w-inset);
CGContextAddArcToPoint(ctx,x+w, y+w, x+w-inset, y+w, inset);
CGContextAddLineToPoint(ctx, x+inset, y+w);
CGContextAddArcToPoint(ctx,x, y+w, x, y+w-inset, inset);
CGContextAddLineToPoint(ctx, x, y+inset);
CGContextAddArcToPoint(ctx,x, y, x+inset, y, inset);
}
- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGFloat color[4];color[0] = 1.0;color[1] = 1.0;color[2] = 1.0;color[3] = 1.0;
CGFloat scolor[4];scolor[0] = 0.4;scolor[1] = 0.4;scolor[2] = 0.4;scolor[3] = 0.8;
CGContextSetFillColor(ctx, color);
CGContextSaveGState(ctx);
CGSize myShadowOffset = CGSizeMake (3, -3);
CGContextSetShadow (ctx, myShadowOffset, 1);
CGContextBeginPath(ctx);
[self drawTheRealThingInContext:ctx];
CGContextFillPath(ctx);
CGContextRestoreGState(ctx);
}
Your (original) problem was that you were again drawing a shadow when you drew the gradient. This shadow had a (0,0) offset and a little bit of blur, that only shines through on the corners. In the line before CGContextDrawLinearGradient(…), add the following:
CGContextSetShadowWithColor(context, CGSizeMake(0, 0), 0, NULL);
The NULL color value disables shadowing and will remove the corner effect.
I have not really done much programming with Core Graphics. And I tend to stick with QuartzCore now that it does a lot of what I need through the layer property :)
However, I have a UIView, which is currently gradient. I'd like to add rounded corners to this UIView and the layer property does not do this when I draw the gradient:
- (void)drawRect:(CGRect)rect {
CGContextRef currentContext = UIGraphicsGetCurrentContext();
CGGradientRef glossGradient;
CGColorSpaceRef rgbColorspace;
size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 };
CGFloat components[8] = { 1.0, 1.0, 1.0, 0.95, // Start color
1.0, 1.0, 1.0, 0.60 }; // End color
rgbColorspace = CGColorSpaceCreateDeviceRGB();
glossGradient = CGGradientCreateWithColorComponents(rgbColorspace, components, locations, num_locations);
CGRect currentBounds = self.bounds;
CGPoint topCenter = CGPointMake(CGRectGetMidX(currentBounds), 0.0f);
CGPoint midCenter = CGPointMake(CGRectGetMidX(currentBounds), CGRectGetMaxY(currentBounds));
CGContextDrawLinearGradient(currentContext, glossGradient, topCenter, midCenter, 0);
CGGradientRelease(glossGradient);
CGColorSpaceRelease(rgbColorspace);
}
I am not really sure where I should be rounding in the drawRect method. Thanks.
Have you checked previous question postings? I read one a while back about masking UIViews. I think the same applies pretty much on all objects which use drawRect
How to mask a square image into an image with round corners in the iPhone SDK?
Here's what I did, and it works fine as far as I can tell.
First, I borrowed Mr NilObject's code snippet from the above mentioned post.
I modified it to fit in an object (as he wrote it as a C function instead of a method)
I subclass UIView to create my own custom view. I overload initWithRect: to make my background transparent.
So basically:
set transparent background (in init), or clipping will be uggly
in drawRect, first clip, then draw inside the clipped area
The following is a working example:
//
// TeleView.m
//
#import "TeleView.h"
#implementation TeleView
/**** in init methods, set background to transparent,
otherwise, clipping shows a black background ****/
- (id) initWithFrame:(CGRect)frame {
if((self = [super initWithFrame:frame])) {
[self setBackgroundColor:[UIColor colorWithWhite:0.0f alpha:0.0f]];
}
return self;
}
- (void) clipCornersToOvalWidth:(float)ovalWidth height:(float)ovalHeight
{
float fw, fh;
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect rect = CGRectMake(0.0f, 0.0f, self.frame.size.width, self.frame.size.height);
if (ovalWidth == 0 || ovalHeight == 0) {
CGContextAddRect(context, rect);
return;
}
CGContextSaveGState(context);
CGContextTranslateCTM (context, CGRectGetMinX(rect), CGRectGetMinY(rect));
CGContextScaleCTM (context, ovalWidth, ovalHeight);
fw = CGRectGetWidth (rect) / ovalWidth;
fh = CGRectGetHeight (rect) / ovalHeight;
CGContextMoveToPoint(context, fw, fh/2);
CGContextAddArcToPoint(context, fw, fh, fw/2, fh, 1);
CGContextAddArcToPoint(context, 0, fh, 0, fh/2, 1);
CGContextAddArcToPoint(context, 0, 0, fw/2, 0, 1);
CGContextAddArcToPoint(context, fw, 0, fw, fh/2, 1);
CGContextClosePath(context);
CGContextRestoreGState(context);
}
-(void)drawRect:(CGRect)rect {
CGContextRef currentContext = UIGraphicsGetCurrentContext();
/**** here is what I modified. ****/
[self clipCornersToOvalWidth:20.0f height:20.0f];
CGContextClip(currentContext);
/**** below this is your own code ****/
CGGradientRef glossGradient;
CGColorSpaceRef rgbColorspace;
size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 };
CGFloat components[8] = { 1.0, 0.0, 0.0, 0.60, // Start color
0.0, 1.0, 0.0, 0.40 }; // End color
rgbColorspace = CGColorSpaceCreateDeviceRGB();
glossGradient = CGGradientCreateWithColorComponents(rgbColorspace, components, locations, num_locations);
CGRect currentBounds = self.bounds;
CGPoint topCenter = CGPointMake(CGRectGetMidX(currentBounds), 0.0f);
CGPoint midCenter = CGPointMake(CGRectGetMidX(currentBounds), CGRectGetMaxY(currentBounds));
CGContextDrawLinearGradient(currentContext, glossGradient, topCenter, midCenter, 0);
CGGradientRelease(glossGradient);
CGColorSpaceRelease(rgbColorspace);
}
#end
NSArray *colors = [NSArray arrayWithObjects:fromColor.CGColor,toColor.CGColor, nil];
NSNumber *stopOne = [NSNumber numberWithFloat:0.0];
NSNumber *stopTwo = [NSNumber numberWithFloat:1.0];
NSArray *locations = [NSArray arrayWithObjects:stopOne, stopTwo, nil];
CAGradientLayer *headerLayer = [CAGradientLayer layer];
headerLayer.colors = colors;
headerLayer.locations = locations;
headerLayer.borderColor = borderColor.CGColor; // border line color**strong text**
headerLayer.borderWidth = width;// For Thickness of border line
headerLayer.cornerRadius = radius;//For Rounded Corner if You want to make rounded Corner
headerLayer.frame = frame;
[view.layer insertSublayer:headerLayer atIndex:0];
Also make sure to import "QuartzCore" framework