Clipping inverse drawing? - iphone

I'm want to remove the blue part of the following images. How do I clip the inverse of the drawing? (I believe that's the correct way to ask the question)
Example code for Triangle: (If there's better triangle code, I'd accept that too!;)
int lineWidth = 4;
int w = size.size.width;
int h = size.size.height - lineWidth;
CGPoint top = CGPointMake(0+(w/2)+.5, 0);
CGContextClipToRect(ctx, CGRectMake(0, 0, w, h));
CGContextStrokePath(ctx);
CGContextMoveToPoint(ctx, top.x, top.y);
CGContextAddLineToPoint(ctx, top.x + (w/2), top.y + h );
CGContextAddLineToPoint(ctx, top.x - (w/2), top.y + h );
CGContextAddLineToPoint(ctx, top.x, top.y);
CGContextFillPath(ctx);
CGContextSetLineWidth(ctx, lineWidth);
CGContextSetLineCap(ctx, kCGLineCapRound);
CGContextMoveToPoint(ctx, top.x, top.y);
CGContextAddLineToPoint(ctx, top.x + (w/2), top.y + h );
CGContextAddLineToPoint(ctx, top.x - (w/2), top.y + h );
CGContextAddLineToPoint(ctx, top.x, top.y);
CGContextStrokePath(ctx);

An easy solution is to use UIBazierPath. Draw a Bazier Path according to your intentions. Then call [path addClip] method. It'll clip all the outside the closed path.
For example following code makes your view round cornered.
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:self.bounds
byRoundingCorners:UIRectCornerAllCorners
cornerRadii:CGSizeMake(6.0, 6.0)];
[path addClip];

To clip the inverse of any path just add an enclosing rect to your clip.
In this case, if I get you right, you want to clip around the red stroke of the shapes. Generally, that's not an easy task in quartz due to the line width of the stroked path. Of course, for these two shapes, we can compute the outline of the red stroke and clip around that. But a general solution would need a way to calculate the encompassing path, including the line width.
There are other ways that include bitmap masks—but the best solution would depend on what exactly are you drawing. I just had a similar problem which I could very elegantly and efficiently solve using a specialized quartz blend mode. This depends very much on the contents of the centext before drawing and how the result should be blended with the rest of the UI.

Related

Core Graphics draw line with outline

I'm drawing an arbitrary line with Core Graphics with a width of 4 pixels, now I would like this line to have a 1 pixel outline of another colour. I can't see any CG functions that would achieve this "out of the box" but I'm looking for suggestions on how it could be done. This is my existing code:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, 4.0);
CGPoint curPoint = [(NSValue*)[points objectAtIndex:0] CGPointValue];
CGContextMoveToPoint(context, curPoint.x, curPoint.y);
for( int i = 1; i < [points count]; i++ ) {
curPoint = [(NSValue*)[points objectAtIndex:i] CGPointValue];
CGContextAddLineToPoint(context, curPoint.x, curPoint.y);
CGContextStrokePath(context);
CGContextMoveToPoint(context, curPoint.x, curPoint.y);
}
This produces the single line. I would like to produce a 4px line with a 1px line highlighting the 4px line like this:
iOS 5.0 added a new feature CGPathCreateCopyByStrokingPath() that does what you want.
First create a CGPathRef for the black path, and then create a copy of it with CGPathCreateCopyByStrokingPath().
This will give you a new path, which you can fill in black and stroke in red, to get what you want.
Also, creating paths is a bit slow. You should avoid creating paths while performing screen drawing. All your paths should be stored in RAM and ready to go before you start drawing to the screen. drawRect: should only draw the path, not create it.
Similar to what Abhi Beckert suggests, but you can use this function:
CGContextReplacePathWithStrokedPath(CGContextRef c);
Which is present in older SDKs too - iOS 4.1, MacOS X 10.6, for example.
Also it is better to create the whole path and then stroke it (or stroke and fill in the same time) at the end - in other words no need to have CGContextStrokePath inside the loop.
I am afraid you will have to draw the path again with the line width set to 1. If you want it to be on the outside of your 4 pixel path, you will have to adjust your path accordingly.
Edit: One other option comes to mind - you can stroke a pattern - see Apple's QuartzDemo for an example how.
To add an answer to my own question, it can be done by drawing a line a few pixels wider in the highlight colour followed by the actual line on top. This produces the outline effect.
There isn't a built-in way to convert a stroke to a path, and then stroke that path. That said, you may be able to approximate this by drawing the line twice: once with a 6 pixel stroke (4 pixels + 1 on each side) and then again with a 4 pixel stroke in a different color
Similar to:
CGContextRef context = UIGraphicsGetCurrentContext();
CGPoint curPoint = [(NSValue*)[points objectAtIndex:0] CGPointValue];
CGContextMoveToPoint(context, curPoint.x, curPoint.y);
for( int i = 1; i < [points count]; i++ ) {
curPoint = [(NSValue*)[points objectAtIndex:i] CGPointValue];
CGContextAddLineToPoint(context, curPoint.x, curPoint.y);
}
// Set your 1 pixel highlight color here using CGContextSetRGBStrokeColor or equivalent
// CGContextSetRGBStrokeColor(...)
CGContextSetLineWidth(context, 6.0);
CGContextStrokePath(context);
// Set your 4 pixel stroke color here using CGContextSetRGBStrokeColor or equivalent
// CGContextSetRGBStrokeColor(...)
CGContextSetLineWidth(context, 4.0);
CGContextStrokePath(context);
Another idea would be setting up a shadow via CGContextSetShadowWithColor(context, CGSizeZero, 1.0, yourHighlightColorHere) prior to drawing the stroke, although this won't draw the highlight color with full opacity. (I also can't remember if shadows property shadow strokes - I have only used them with fills)

RotateCTM makes graphics disappear, Quartz 2D

First off, I am a quartz 2d noob. I have a pie chart, and basically want to make a line coming out of it diagonally, in the northeast quadrant, then go horizontal to the right, and have a label explaining what that section is. So, I have this code that draws the diagonal line. The line draws to the southeast. I expect that based on the iPhone origin being in the northwest quadrant. So I thought I could rotate my CTM first by 3pi/2, and then have that same code work. However, when I uncomment the CGContextRotateCTM() line, I get no line. I'm not sure what is happening here and where the logic error is. Thanks.
CGPoint circleCenter = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);
CGContextSetFillColorWithColor(context, [[UIColor whiteColor] CGColor]);
CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0);
CGContextSetLineWidth(context, LINE_WIDTH);
CGContextBeginPath(context);
// CGContextRotateCTM(context, 3 * M_PI / 2);
CGContextMoveToPoint(context, circleCenter.x + self.CircleRadius / 2, circleCenter.y + self.CircleRadius / 2);
CGContextAddLineToPoint(context, circleCenter.x + self.CircleRadius / 2 + LINE_LENGTH, circleCenter.y + self.CircleRadius / 2 + LINE_LENGTH);
CGContextStrokePath(context);
It seems you are rotating your line out of sight. Always keep the center in mind that you rotate around - maybe also translate your CTM first.
Edit: To check, you might want to try very small rotations incrementally and watch where your line goes.

I need to draw a line on a view, and have the line be straight no matter what, but dynamic in length

Ok I need to implement a simple measuring tool for an iPad app I am working on. I don't have any experience with drawing, so I am really struggling. When the user presses down on the view (in measuring mode) the line's origin starts. I need to then be able to draw a line to wherever the user drags their finger, as they are dragging their finger, and have it be straight the entire time too.
I have the logic that calculates the distance between two points working based on two UITapGestureRecognizers, but I am thinking I will need to implement the touchesBegan/Ended methods instead.
How can I draw a line as the user drags, and make it stay straight the entire time?
I just need a point in the right direction.
Thanks!!
Check out a good tutorial like this.
Skip to the section on drawing lines:
void draw1PxStroke(CGContextRef context, CGPoint startPoint, CGPoint endPoint,
CGColorRef color) {
CGContextSaveGState(context);
CGContextSetLineCap(context, kCGLineCapSquare);
CGContextSetStrokeColorWithColor(context, color);
CGContextSetLineWidth(context, 1.0);
CGContextMoveToPoint(context, startPoint.x + 0.5, startPoint.y + 0.5);
CGContextAddLineToPoint(context, endPoint.x + 0.5, endPoint.y + 0.5);
CGContextStrokePath(context);
CGContextRestoreGState(context);
}
and then here is the call to that method:
// Add in color section
CGColorRef separatorColor = [UIColor colorWithRed:208.0/255.0 green:208.0/255.0
blue:208.0/255.0 alpha:1.0].CGColor;
// Add at bottom
CGPoint startPoint = CGPointMake(0, 0);
CGPoint endPoint = CGPointMake(100, 20);
draw1PxStroke(context, startPoint, endPoint, separatorColor);

Drawing a solid line in a UITableViewCell

I don't know anything about graphics or drawing so I assume that's why I can't figure out what to look for to solve this.
Here's what I got in my UITableViewCell's drawRect
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(ctx, 0.0, 0.0, 0.0, 1.0);
CGContextSetLineWidth(ctx, 0.25);
for (int i = 0; i < [columns count]; i++) {
CGFloat f = [((NSNumber*) [columns objectAtIndex:i]) floatValue];
CGContextMoveToPoint(ctx, f, 0);
CGContextAddLineToPoint(ctx, f, self.bounds.size.height);
}
CGContextStrokePath(ctx);
}
I think this should be drawing a black line but the line is gray, blending with what's in the background. How can I draw a solid line that is not influenced by a background image? I've tried all the blend mode's thinking maybe one would be like a blend mode none, because I don't want any blending, but all draw lines that blend in some way with the background.
You can try turning off antialiasing using CGContextSetShouldAntialias(). Also, 0.25 seems like a pretty thin line. Setting it to 1.0 might give you what you're looking for.
Two problems:
* You're drawing a black line that's 0.25 pixels (or 0.5 pixels on iPhone 4) wide; antialiasing will make this appear semi-transparent. Try setting the line width to 1.
* If f is an integer, then the center of the line is aligned on a pixel boundary (you want it on a pixel center). Try adding 0.5f.
If you want thinner lines on iPhone 4, then check if self.layer.scale == 2 and if so, set the line width to 0.5 and add 0.25f.
Alternatively, set the fill colour and call CGContextFillRect() instead, which means you don't have to worry about pixel centers.
UIView *line = [[UIView alloc]initWithFrame:CGRectMake(100,0,1,44)];
line.backgroundColor = [UIColor colorWithRed:244.0f/255.0f green:244.0f/255.0f blue:244.0f/255.0f alpha:1];
[cell addSubview:line];

Best way to accomplish this drawing with Quartz 2D / Core Graphics?

As the background for one of the views in my app, I'd like to draw a fairly simple rectangular border just inside its frame. This would essentially be a rectangular gradient: a black line around the frame, fading to white about 10-20 pixels in. Unfortunately, as far as I can tell, Core Graphics doesn't provide rectangular gradients (either with CGGradient or CGShading). So I'm wondering what the best approach would be.
Two that occur to me:
Draw a series of concentric rectangles, each subsequent one lighter in color, and inset by 1px on each side. I can't think of a simpler approach, but I have to do all of the gradient calculations myself, and it might be a lot of graphics operations.
Use CGGradient in linear mode, once for each side. But for this to work, I think I'd need to set up a trapezoidal clipping area for each side first, so that the gradients would be mitered at the corners.
Seems like there should be a way to use path stroking to do this, but it doesn't seem like there's a way to define a pattern that's oriented differently on each side.
I would go with option #2:
Use CGGradient in linear mode, once for each side. But for this to work, I think I'd need to set up a trapezoidal clipping area for each side first, so that the gradients would be mitered at the corners.
Using NSBezierPath to create the trapezoidal regions would be fairly straightforward, and you would only have to perform four drawing operations.
Here's the basic code for creating the left side trapezoidal region:
NSRect outer = [self bounds];
NSPoint outerPoint[4];
outerPoint[0] = NSMakePoint(0, 0);
outerPoint[1] = NSMakePoint(0, outer.size.height);
outerPoint[2] = NSMakePoint(outer.size.width, outer.size.height);
outerPoint[3] = NSMakePoint(outer.size.width, 0);
NSRect inner = NSInsetRect([self bounds], borderSize, borderSize);
NSPoint innerPoint[4];
innerPoint[0] = inner.origin;
innerPoint[1] = NSMakePoint(inner.origin.x,
inner.origin.y + inner.size.height);
innerPoint[2] = NSMakePoint(inner.origin.x + inner.size.width,
inner.origin.y + inner.size.height);
innerPoint[3] = NSMakePoint(inner.origin.x + inner.size.width,
inner.origin.y);
NSBezierPath leftSidePath = [[NSBezierPath bezierPath] retain];
[leftSidePath moveToPoint:outerPoint[0]];
[leftSidePath lineToPoint:outerPoint[1]];
[leftSidePath lineToPoint:innerPoint[1]];
[leftSidePath lineToPoint:innerPoint[0]];
[leftSidePath lineToPoint:outerPoint[0]];
// ... etc.
[leftSidePath release];
something like this could also work.
basically: instead of using clipping paths, simply use blendmode.
and in this example the gradient is cached in a CGLayer.
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGColorSpaceRef cspace = CGColorSpaceCreateDeviceRGB();
CGContextSetRGBFillColor(ctx, 1.0, 1.0, 1.0, 1.0);
CGContextFillRect(ctx,self.bounds);
CGFloat w = self.bounds.size.width;
CGFloat h = self.bounds.size.height;
CGFloat dh = (w-h)/2;
CGLayerRef l = CGLayerCreateWithContext(ctx,CGSizeMake(h,48.0f),NULL);
CGContextRef lctx = CGLayerGetContext(l);
float comp[] = { .2,.5,1.0,1.0,1.0,1.0,1.0,1.0};
CGGradientRef gradient = CGGradientCreateWithColorComponents(cspace, comp, NULL, 2);
CGContextDrawLinearGradient(lctx, gradient,CGPointMake(0,0),CGPointMake(0,48), 0);
CGContextSaveGState(ctx);
CGContextSetBlendMode(ctx,kCGBlendModeDarken);
for(int n=1;n<5;n++)
{
CGContextTranslateCTM(ctx,w/2.0,h/2.0);
CGContextRotateCTM(ctx, M_PI_2);
CGContextTranslateCTM(ctx,-w/2.0,-h/2.0);
CGContextDrawLayerAtPoint(ctx,CGPointMake((n%2)*dh,(n%2)*-dh),l);
}
CGContextRestoreGState(ctx);