I have a question about Quartz and clipping area:
I would like to have a rectangle A
inside this rectangle I would like to have a rectangle B
The filling of B dveve also cut in A: I would like that A was pierced by B. What is the best way to do this in quartz? I did not really understand how the clipping
If I understand you correctly, you want to draw a smaller rectangle inside a larger rectangle so that the inner rectangle is transparent. You can achieve this by drawing a CAShapeLayer with a path that contains both rectangles as subpaths. Don't forget to set the layer's fill rule to kCAFillRuleEvenOdd.
Try something like this:
CGRect rectA = CGRectMake(100, 100, 200, 200);
CGRect rectB = CGRectMake(150, 150, 100, 100);
UIBezierPath *path=[[UIBezierPath alloc] init];
// Add sub-path for rectA
[path moveToPoint:CGPointMake(rectA.origin.x, rectA.origin.y)];
[path addLineToPoint:CGPointMake(rectA.origin.x+rectA.size.width, rectA.origin.y)];
[path addLineToPoint:CGPointMake(rectA.origin.x+rectA.size.width, rectA.origin.y+rectA.size.height)];
[path addLineToPoint:CGPointMake(rectA.origin.x, rectA.origin.y+rectA.size.height)];
[path closePath];
// Add sub-path for rectB
[path moveToPoint:CGPointMake(rectB.origin.x, rectB.origin.y)];
[path addLineToPoint:CGPointMake(rectB.origin.x+rectB.size.width, rectB.origin.y)];
[path addLineToPoint:CGPointMake(rectB.origin.x+rectB.size.width, rectB.origin.y+rectB.size.height)];
[path addLineToPoint:CGPointMake(rectB.origin.x, rectB.origin.y+rectB.size.height)];
[path closePath];
// Create CAShapeLayer with this path
CAShapeLayer *pathLayer = [CAShapeLayer layer];
[pathLayer setFillRule:kCAFillRuleEvenOdd]; /* <- IMPORTANT! */
[pathLayer setPath:path.CGPath];
[pathLayer setFillColor:[UIColor blackColor].CGColor];
// Add the CAShapeLayer to a view
[someView.layer addSublayer:pathLayer];
I solved with this simple way:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBFillColor(context, 0.0, 0.0, 0.0, 0);
CGContextFillRect(context,self.bounds);
CGContextAddRect(context, self.bounds);
//Add cropped rectangle:
CGContextAddRect(context, _croppedRegion);
//Clip:
CGContextEOClip(context);
CGContextSetRGBFillColor(context, 255.0, 255.0, 255.0, 0.5);
CGContextFillRect(context, self.bounds);
}
Related
I want to draw a delete button just like the delete app button on home screen like below code.
The idea is draw the cross first, and then rotate 45 degree. What's wrong with my code?
self.deleteButton = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, BADGE_SIZE, BADGE_SIZE)];
if (!deleteBtnImg) {
CGRect imgFrame = self.deleteButton.bounds;
UIGraphicsBeginImageContextWithOptions(imgFrame.size, NO, 0.0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGFloat size = MIN(imgFrame.size.width, imgFrame.size.height);
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(imgFrame.size.width/2, imgFrame.size.height/2)
radius:size/2-RADIUS_MARGIN
startAngle:0
endAngle:M_PI * 2
clockwise:YES];
[path moveToPoint:CGPointMake(size / 2, 0)];
[path addLineToPoint:CGPointMake(size / 2, size)];
[path moveToPoint:CGPointMake(0, size / 2)];
[path addLineToPoint:CGPointMake(size, size / 2)];
[[UIColor whiteColor] setFill];
[[UIColor redColor] setStroke];
[path setLineWidth:1.0];
[path fill];
[path stroke];
CGContextRotateCTM(context, M_PI/4);
deleteBtnImg = UIGraphicsGetImageFromCurrentImageContext();
CGContextRestoreGState(context);
UIGraphicsEndImageContext();
}
[self.deleteButton setImage:deleteBtnImg forState:UIControlStateNormal];
You have to rotate the context before start drawing. However even if you rotate before drawing. The image still does not look right. It is because when you rotate the context, the context is actually rotating around it's origin point which is (0,0) or the bottom-left corner (The CoreGraphic's coordinate system is a bit different from UI's). So what you could do is, before rotation, translate the context so that it will rotate around its center, and then move it back after rotation. Here is a quick example:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGFloat size = MIN(imgFrame.size.width, imgFrame.size.height);
CGContextTranslateCTM(context, size / 2, size / 2);
CGContextRotateCTM(context, M_PI_4);
CGContextTranslateCTM(context, -size / 2, -size / 2);
// Start your drawing code
I am using lineWidth to create a circle with stroke width 4. I am using UIBezierPath for creating the circle. But, due to some reason, lineWidth is always rendering a circle with a thin stroke no matter what value I assign to lineWidth. I have also tried to put path.lineWidth = 100.0, but no change in stroke width.
This is my code:
UIGraphicsBeginImageContextWithOptions(CGSizeMake(progressView.frame.size.width, progressView.frame.size.height), NO, 0.0);
[[UIColor colorWithRed:246.0/255.0 green:80.0/255.0 blue:36.0/255.0 alpha:1.0] setStroke];
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(progressView.frame.size.width/2 ,progressView.frame.size.height/2) radius:progressView.frame.size.width/2 - 5 startAngle:DEGREES_TO_RADIANS(0) endAngle:DEGREES_TO_RADIANS(angle) clockwise:YES];
[path stroke];
path.lineWidth = 4;
UIImage* pathImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[progressView setImage:pathImg];
I googled a lot, but could not find any solution to this problem.
You need to set the stroke width before you actually stroke the path:
UIGraphicsBeginImageContextWithOptions(CGSizeMake(progressView.frame.size.width, progressView.frame.size.height), NO, 0.0);
[[UIColor colorWithRed:246.0/255.0 green:80.0/255.0 blue:36.0/255.0 alpha:1.0] setStroke];
UIBezierPath *path = [UIBezierPath bezierPathWithArcCenter:CGPointMake(progressView.frame.size.width/2 ,progressView.frame.size.height/2) radius:progressView.frame.size.width/2 - 5 startAngle:DEGREES_TO_RADIANS(0) endAngle:DEGREES_TO_RADIANS(angle) clockwise:YES];
path.lineWidth = 4;
[path stroke];
UIImage* pathImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[progressView setImage:pathImg];
I am trying to draw circles on an image view for an ios application and there will be many circles and I want them to be in the same layer. My circle drawing code is;
UIBezierPath *circle = [UIBezierPath bezierPathWithArcCenter:center
radius:radius
startAngle:0
endAngle:2.0*M_PI
clockwise:YES];
CAShapeLayer *circleLayer = [CAShapeLayer layer];
circleLayer.bounds = CGRectMake(0, 0, 2.0*radius, 2.0*radius);
circleLayer.path = circle.CGPath;
circleLayer.backgroundColor = [UIColor orangeColor].CGColor;
and I need some thing different than the code below;
[imageView.layer addSublayer:circleLayer];
thanks.
From this site: http://www.cocoanetics.com/2010/07/drawing-on-uiimages/
- (UIImage *)imageByDrawingCircleOnImage:(UIImage *)image
{
// begin a graphics context of sufficient size
UIGraphicsBeginImageContext(image.size);
// draw original image into the context
[image drawAtPoint:CGPointZero];
// get the context for CoreGraphics
CGContextRef ctx = UIGraphicsGetCurrentContext();
// set stroking color and draw circle
[[UIColor redColor] setStroke];
// make circle rect 5 px from border
CGRect circleRect = CGRectMake(0, 0,
image.size.width,
image.size.height);
circleRect = CGRectInset(circleRect, 5, 5);
// draw circle
CGContextStrokeEllipseInRect(ctx, circleRect);
// make image out of bitmap context
UIImage *retImage = UIGraphicsGetImageFromCurrentImageContext();
// free the context
UIGraphicsEndImageContext();
return retImage;
}
Is it possible to draw the following in objective-c? I tried using the image, but It pixelates. So I figured it is best to draw it programatically. Can someone provide me some sample code to achieve this.
Thank you.
Use a CAShapeLayerand assign it a path describing your shape:
- (UIBezierPath *)createBubblePathInFrame:(CGRect) frame{
CGFloat arrowInset = 10;
CGFloat arrowHeight = 10;
CGFloat arrowWidth = 20;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, arrowHeight)];
[path addLineToPoint:CGPointMake(arrowInset, arrowHeight)];
[path addLineToPoint:CGPointMake(arrowInset+arrowWidth/2, 0)];
[path addLineToPoint:CGPointMake(arrowInset+arrowWidth, arrowHeight)];
[path addLineToPoint:CGPointMake(frame.size.width, arrowHeight)];
[path addLineToPoint:CGPointMake(frame.size.width, frame.size.height)];
[path addLineToPoint:CGPointMake(0, frame.size.height)];
[path addLineToPoint:CGPointMake(0, arrowHeight)];
return path;
}
Use it like this:
CAShapeLayer *shapelayer = [CAShapeLayer layer];
shapelayer.frame = CGRectMake(50, 50, 200, 100);
shapelayer.path = [self createBubblePathInFrame:shapelayer.bounds].CGPath;
shapelayer.fillColor = [UIColor grayColor].CGColor;
shapelayer.strokeColor = [UIColor redColor].CGColor;
shapelayer.lineWidth = 1;
[self.view.layer addSublayer:shapelayer];
Or you can draw it yourself in drawRect:
[[UIColor grayColor] setFill];
[bezierPath fill];
[[UIColor redColor] setStroke];
[bezierPath stroke];
You can use this as a triangle view:
#implementation Triangle
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self)
{
self.backgroundColor = [UIColor clearColor];
self.alpha = 0.75;
}
return self;
}
- (void)drawRect:(CGRect)rect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, CGRectGetMinX(rect), CGRectGetMinY(rect)); // top left
CGContextAddLineToPoint(ctx, CGRectGetMaxX(rect), CGRectGetMinY(rect)); // top right
CGContextAddLineToPoint(ctx, CGRectGetMidX(rect), CGRectGetMaxY(rect)); // bottom mid
CGContextClosePath(ctx);
CGContextSetRGBFillColor(ctx, 25.0/255.0, 25.0/255.0, 25.0/255.0, 1.0);
CGContextFillPath(ctx);
}
#end
Then just subclass a UIView and add an instance of Triangle where appropriate (in your case on the top left corner).
For your purpose you also should rotate this triangle 180 degrees as the code above drwas a triangle that points downwards.
I'm having trouble with drawing some lines that are stroked with a color and then filling their insides (they make a polygon) with another one.
UIColor *houseBorderColor = [UIColor colorWithRed:(170/255.0) green:(138/255.0) blue:(99/255.0) alpha:1];
CGContextSetStrokeColorWithColor(context, houseBorderColor.CGColor);
CGContextSetLineWidth(context, 3);
// Draw the polygon
CGContextMoveToPoint(context, 20, viewHeight-19.5);
CGContextAddLineToPoint(context, 200, viewHeight-19.5); // base
CGContextAddLineToPoint(context, 300, viewHeight-119.5); // right border
CGContextAddLineToPoint(context, 120, viewHeight-119.5);
CGContextAddLineToPoint(context, 20, viewHeight-19.5);
// Fill it
CGContextSetRGBFillColor(context, (248/255.0), (222/255.0), (173/255.0), 1);
//CGContextFillPath(context);
// Stroke it
CGContextStrokePath(context);
With the CGContextStrokePath commented out, I get this result:
But if I uncomment CGContextStrokePath and fill out the polygon, the color overflows the strokes:
How do you achieve a result like this (without having to redo the whole drawing procedure twice):
You can use
CGContextDrawPath(context, kCGPathFillStroke);
instead of
CGContextFillPath(context);
CGContextStrokePath(context);
The problem is that both CGContextFillPath() and CGContextStrokePath(context)
clear the current path, so that only the first operation succeeds, and the second
operation draws nothing. CGContextDrawPath() combines fill and stroke without
clearing the path in between.
Using UIBezierPath you could do this:
UIBezierPath *path = [[UIBezierPath alloc] init];
[path moveToPoint:CGPointMake(20, viewHeight-19.5)];
[path addLineToPoint:CGPointMake(200, viewHeight-19.5)];
[path addLineToPoint:CGPointMake(300, viewHeight-119.5)];
[path addLineToPoint:CGPointMake(120, viewHeight-119.5)];
[path addLineToPoint:CGPointMake(20, viewHeight-19.5)];
[[UIColor colorWithRed:(248/255.0) green:(222/255.0) blue:(173/255.0) alpha:1.0] setFill];
[path fill];
[[UIColor colorWithRed:(170/255.0) green:(138/255.0) blue:(99/255.0) alpha:1.0] setStroke];
[path stroke];
When you stroke or fill the path in the context, the context removes the path for you (it expects that it's work is done). You must add the path again if you want to fill it after stroking it.
It's probably best to create a CGPathRef path local variable, build the path, add it, stroke, add it again, fill.
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, nil, 20, viewHeight-19.5);
CGPathAddLineToPoint(path nil, 200, viewHeight-19.5); // base
CGPathAddLineToPoint(path nil, 300, viewHeight-119.5); // right border
CGPathAddLineToPoint(path nil, 120, viewHeight-119.5);
CGPathAddLineToPoint(path nil, 20, viewHeight-19.5);
CGContextAddPath(context, path);
CGContextFillPath(context);
// possibly modify the path here if you need to
CGContextAddPath(context, path);
CGContextStrokePath(context);