Reputation: 319
I am attempting to draw some shapes in my app using Core Graphics and I am having some issues understanding the best practices for masking multiple shapes into one.
This is the shape I want to end up with (including colours).
Circle http://www.myquapps.com/help/lvl1Circle1.png
So far I have drawn a circle the same size with the correct stroke like so:-
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextClearRect(ctx, rect);
CGContextSetRGBFillColor(ctx, 0, 0, 0, 1);
CGFloat red[4] = {1.0f, 0.0f, 0.0f, 1.0f};
CGContextSetStrokeColor(ctx, red);
CGContextSetLineWidth(ctx, 18);
CGContextStrokeEllipseInRect(ctx, CGRectMake(9, 9, 232, 232));
CGContextFillPath(ctx);
Any help here would be greatly appreciated! This is my first run-in with Core Graphics and it looks to be exactly what I need!
Upvotes: 1
Views: 2751
Reputation: 55593
Took me a bit, but here you go (all bezier paths are instance variables):
EDIT: Made the inner of the circle transparent, but brace yourself, this gets ugly FAST!
#define LARGE_RADIUS 200
#define SMALL_RADIUS 160
#define RADIANS(degrees) ((degrees) / (180.0 / M_PI))
@implementation MyView
{
UIBezierPath *largeCircle;
UIBezierPath *orangeWedge;
UIBezierPath *yellowWedge;
UIBezierPath *greenWedge;
UIBezierPath *whiteCircle;
UIImage *image;
void *_imageData;
}
-(void) createPaths {
largeCircle = [UIBezierPath bezierPathWithArcCenter:self.center radius:LARGE_RADIUS startAngle:0 endAngle:RADIANS(360) clockwise:YES];
whiteCircle = [UIBezierPath bezierPathWithArcCenter:self.center radius:SMALL_RADIUS startAngle:0 endAngle:RADIANS(360) clockwise:YES];
orangeWedge = [UIBezierPath bezierPath];
[orangeWedge moveToPoint:self.center];
double startAngle = RADIANS(45);
double endAngle = RADIANS(135);
[orangeWedge addLineToPoint:CGPointMake(sin(startAngle) + self.center.x, cos(startAngle) + self.center.y)];
[orangeWedge addArcWithCenter:self.center radius:LARGE_RADIUS startAngle:startAngle endAngle:endAngle clockwise:YES];
[orangeWedge addLineToPoint:self.center];
startAngle = RADIANS(60);
endAngle = RADIANS(120);
yellowWedge = [UIBezierPath bezierPath];
[yellowWedge moveToPoint:self.center];
[yellowWedge addLineToPoint:CGPointMake(sin(startAngle) + self.center.x, cos(startAngle) + self.center.y)];
[yellowWedge addArcWithCenter:self.center radius:LARGE_RADIUS startAngle:startAngle endAngle:endAngle clockwise:YES];
[yellowWedge addLineToPoint:self.center];
startAngle = RADIANS(75);
endAngle = RADIANS(105);
greenWedge = [UIBezierPath bezierPath];
[greenWedge moveToPoint:self.center];
[greenWedge addLineToPoint:CGPointMake(sin(startAngle) + self.center.x, cos(startAngle) + self.center.y)];
[greenWedge addArcWithCenter:self.center radius:LARGE_RADIUS startAngle:startAngle endAngle:endAngle clockwise:YES];
[greenWedge addLineToPoint:self.center];
}
-(void) drawPaths
{
[[UIColor blackColor] setStroke];
[[UIColor redColor] setFill];
[largeCircle fill];
[largeCircle stroke];
[[UIColor orangeColor] setFill];
[orangeWedge fill];
[orangeWedge stroke];
[[UIColor yellowColor] setFill];
[yellowWedge fill];
[yellowWedge stroke];
[[UIColor greenColor] setFill];
[greenWedge fill];
[greenWedge stroke];
[whiteCircle stroke];
}
-(int32_t *) pixelAt:(int) x :(int) y
{
return &((int32_t *)_imageData)[x + (y * (int) self.frame.size.width)];
}
-(void) removeInnerCircle
{
int32_t invisible = 0;
int centerX = self.center.x;
int centerY = self.center.y;
for (int i = SMALL_RADIUS - 1; i > 0; i--) {
float incr = (22.0 / i);
for (float j = 0; j <= 360; j += incr) {
float angle = RADIANS(j);
*[self pixelAt:(sin(angle) * i + centerX) :(cos(angle) * i + centerY)] = invisible;
}
}
}
-(void) setUpContext
{
size_t width = self.frame.size.width;
size_t height = self.frame.size.height;
_imageData = malloc(4 * width * height);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(_imageData, width, height, 8, width * 4, colorSpace, kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(colorSpace);
CGContextTranslateCTM(context, 0, height);
CGContextScaleCTM(context, 1, -1);
assert(context != NULL);
UIGraphicsPushContext(context);
CGContextRelease(context);
}
-(void) destroyContext
{
CGImageRef imageRef = CGBitmapContextCreateImage(UIGraphicsGetCurrentContext());
assert(imageRef != NULL);
image = [UIImage imageWithCGImage:imageRef];
UIGraphicsPopContext();
free(_imageData);
_imageData = NULL;
}
-(void) awakeFromNib
{
[self createPaths];
[self setUpContext];
[self drawPaths];
[self removeInnerCircle];
[self destroyContext];
}
-(void) drawRect:(CGRect)rect
{
[image drawInRect:self.frame];
}
@end
Depending on the value of SMALL_RADIUS
, you may wish to change the increment of j
in -removeInnerCircle
, so that it doesn't perform more loops than necessary.
Changed code to make the increment of j
based on the current radius, so the closer to the center you get, the fewer pixels need to be changed!
Upvotes: 4
Reputation: 90721
I would set the path to be the ring shape itself by creating a circle path, setting a thick line width, and then using CGContextReplacePathWithStrokedPath()
. That will make a new path which is effectively two concentric circles.
CGContextBeginPath(ctx);
CGRect rect = CGRectMake(9, 9, 232, 232);
CGContextAddEllipseInRect(ctx, CGRectInset(rect, 16, 16));
CGContextSetLineWidth(ctx, 32);
CGContextReplacePathWithStrokedPath(ctx);
Now you can set the line thickness and color for the black edges of the ring and stroke it. This gets both the inner and outer black concentric circles:
CGContextSetLineWidth(ctx, 32);
CGContextSetGrayStrokeColor(ctx, 0, 1);
CGContextStrokePath(ctx);
Set the fill color and fill it:
CGContextSetRGBFillColor(ctx, 1, 0, 0, 1);
CGContextFillPath(ctx);
Now clip to it:
CGContextClip(ctx);
Now you can draw the orange, yellow, and green shapes they will be clipped to within the ring. It's not clear to me if Richard's code gets those shapes quite right. He uses wedges centered at the ring's center, but in your picture the edges don't seem to all point to the center. (Also, he paints a white circle to "erase" the interior part, while your picture is transparent in the interior.) With the clipping set up, you can just draw triangles or whatever is most convenient to get those edges angled the way you want them and they'll automatically be constrained to the ring.
Upvotes: 2