How to draw a double dashed line in Quartz - iphone

I am trying to create an app that allows the user to draw a dotted line with their finger on an iPad. I have all of the drawing routines to create single lines. My question is how should I go about drawing a double line in such a way that it will not overlap itself when you create curves. I have been trying to accomplish this by Adding a second line to my Path with an offset of the x and y of the current point. This works fine as long as you move vertically or horizontally. When you draw at an angle, the double lines become a single line.
I am keeping an array of points stored as NSValues (pathPoints) so that I can store them for later use. Starting point is the first point in pathPoints.
UIGraphicsBeginImageContext(self.frame.size);
//This needs to be a dotted line
CGFloat pattern[] = {10,20};
CGContextSetLineDash(UIGraphicsGetCurrentContext(), 3, pattern, 2);
CGMutablePathRef path = CGPathCreateMutable();
CGMutablePathRef path2 = CGPathCreateMutable();
CGPoint startingPoint = [[pathPoints objectAtIndex:0] CGPointValue];
CGPathMoveToPoint(path, nil, startingPoint.x, startingPoint.y);
CGPathMoveToPoint(path2, nil, startingPoint.x + 10, startingPoint.y + 10);
for (int i = 1; i < [pathPoints count]; i++)
{
CGPoint linePoint = [[pathPoints objectAtIndex:i] CGPointValue];
CGPathAddLineToPoint(path, nil, linePoint.x, linePoint.y);
CGPathAddLineToPoint(path2, nil, linePoint.x + 10, linePoint.y + 10);
}
// of course later on I add the paths to the context and stroke them.
CGContextAddPath(UIGraphicsGetCurrentContext(), path);
CGContextAddPath(UIGraphicsGetCurrentContext(), path2);
CGContextStrokePath(UIGraphicsGetCurrentContext());
UIGraphicsEndImageContext();
I greatly appreciate any help I can get with this.

If you're okay with the lines being joined at the end (see image below) then you can create the effect using CGContextReplacePathWithStrokedPath().
/**
* CGContextRef context = <the context you're drawing to>
* CGFloat lineWidth = <the width of each of your two lines>
* CGFloat lineSpace = <the space between the two lines>
**/
// Create your path here (same as you would for a single line)
CGContextSetLineWidth(context, lineWidth + lineSpace);
CGContextReplacePathWithStrokedPath(context);
CGContextSetLineWidth(context, lineWidth);
CGContextStrokePath(context);

Related

Can't seem to git rid of line in UIBezierPath

OK, so I needed a rounded triangle. So what I did was use a technique similar to what I've used in other vector drawing programs. Draw the triangle and use the stroke to create the rounded corners. Worked like a charm too, until I needed to reduce the alpha of the color used to fill and stroke the UIBezierPath. For some reason I keep getting this inset outline that isn't the same color as the Fill and Stroke. Somehow the alpha value isn't being respected. Maybe I'm overlooking something silly here, but try as I might I can't get the triangle all one color with a alpha value lower than 1. This is what I get:
And heres the simple code:
- (void)drawRect:(CGRect)rect
{
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint: CGPointMake(63.5, 10.5)];
[path addLineToPoint: CGPointMake(4.72, 119.5)];
[path addLineToPoint: CGPointMake(122.28, 119.5)];
[path addLineToPoint: CGPointMake(63.5, 10.5)];
[path closePath];
path.miterLimit = 7;
path.lineCapStyle = kCGLineCapRound;
path.lineJoinStyle = kCGLineJoinRound;
path.lineWidth = 8;
UIColor *whiteAlph5 = [UIColor colorWithWhite:0.6 alpha:0.5];
[whiteAlph5 setFill];
[whiteAlph5 setStroke];
[path fill];
[path stroke];
}
I can't understand why the line would be anything other than the "whiteAlpha5" if that's the only color I've set for both fill and stroke. I suppose I can just draw the rounded triangle out adding the curves to to corners, but I'm just curious as to why this happens.Thanks in advance...
If you must have the stroke, alter your call to [UIBezierPath stroke] like so:
[path fill];
[path strokeWithBlendMode:kCGBlendModeCopy alpha:1.0];
This should achieve the effect you want (I think - haven't been able to test it)
This is a bit of a guess, but I think you're seeing here is essentially two layers of semitransparent white, one drawn on top of the other. When the triangle is just filled in, it would be what you're expecting. When you stroke, it's drawing the same colour - but it's adding it on top of the existing colour, not replacing it, which is the effect you might expect if you've done this before in paint programs or similar. Thus, where the stroke and fill overlap, you're getting a stronger white than you're after. Just using fill by itself could solve this, but might not get the rounded effect you're after.
If you need a visual demonstration of what I mean, you can do this in Photoshop. Create a new image with a black background and create a new layer above it, set to 50% opacity. Draw a white square on it (which will look grey due to the opacity). Then, without changing layers, draw a line through it. You won't see the line, because it's replacing the existing colour - this is what you expected to happen with your code. Then, add another layer above it, also set to 50% opacity. Draw a line on this layer, through the square. You'll see the line as a brighter grey. This is additive, the white overlapping on both layers - the effect that your code is creating.
The line is because your stroke and your fill are drawing to the same pixels. Since both the stroke and the fill are partially transparent, the colors accumulate.
One way to fix this is to just create a path that outlines your rounded triangle, and fill it without stroking it.
Here's the interface for a category that creates a path outlining a rounded polygon:
#interface UIBezierPath (MyRoundedPolygon)
+ (UIBezierPath *)my_roundedPolygonWithSides:(int)sides center:(CGPoint)center
vertexRadius:(CGFloat)vertexRadius cornerRadius:(CGFloat)cornerRadius
rotationOffset:(CGFloat)rotationOffset;
#end
Here's the implementation:
#implementation UIBezierPath (MyRoundedPolygon)
static CGPoint vertexForPolygon(int sides, CGPoint center, CGFloat circumradius, CGFloat index) {
CGFloat angle = index * 2 * M_PI / sides;
return CGPointMake(center.x + circumradius * cosf(angle),
center.y + circumradius * sinf(angle));
}
+ (UIBezierPath *)my_roundedPolygonWithSides:(int)sides center:(CGPoint)center
vertexRadius:(CGFloat)vertexRadius cornerRadius:(CGFloat)cornerRadius
rotationOffset:(CGFloat)rotationOffset
{
CGFloat circumradius = vertexRadius + cornerRadius;
CGPoint veryLastVertex = vertexForPolygon(sides, center, circumradius, rotationOffset - 1);
CGPoint currentVertex = vertexForPolygon(sides, center, circumradius, rotationOffset);
CGMutablePathRef cgpath = CGPathCreateMutable();
CGPathMoveToPoint(cgpath, NULL, (veryLastVertex.x + currentVertex.x) / 2,
(veryLastVertex.y + currentVertex.y) / 2);
for (CGFloat i = 0; i < sides; ++i) {
CGPoint nextVertex = vertexForPolygon(sides, center, circumradius,
i + 1 + rotationOffset);
CGPathAddArcToPoint(cgpath, NULL, currentVertex.x, currentVertex.y,
nextVertex.x, nextVertex.y, cornerRadius);
currentVertex = nextVertex;
}
CGPathCloseSubpath(cgpath);
UIBezierPath *path = [self bezierPathWithCGPath:cgpath];
CGPathRelease(cgpath);
return path;
}
#end
Here's how you use it:
#implementation MyView
- (void)drawRect:(CGRect)rect
{
CGRect bounds = self.bounds;
CGPoint center = CGPointMake(CGRectGetMidX(bounds), CGRectGetMidY(bounds));
UIBezierPath *path = [UIBezierPath my_roundedPolygonWithSides:3 center:center
vertexRadius:70 cornerRadius:8 rotationOffset:0.25];
[[UIColor colorWithWhite:0.6 alpha:0.5] setFill];
[path fill];
}
#end
And here's the result:
Note that setting rotationOffset to 0.25 rotated the triangle one quarter turn. Setting it to zero will give you a right-pointing triangle.

Coloring In between CGPaths

I am building a drawing type tool into my app.
It takes a touch points from the user and draws lines between the points. If the user creates 3 touch points or greater, it joins the last point to the first point.
An extract of the code is :
startPoint = [[secondDotsArray objectAtIndex:i] CGPointValue];
endPoint = [[secondDotsArray objectAtIndex:(i + 1)] CGPointValue];
CGContextAddEllipseInRect(context,(CGRectMake ((endPoint.x - 5.7), (endPoint.y - 5.7)
, 9.0, 9.0)));
CGContextDrawPath(context, kCGPathFill);
CGContextMoveToPoint(context, startPoint.x, startPoint.y);
CGContextAddLineToPoint(context, endPoint.x, endPoint.y);
CGContextStrokePath(context);
I wish to "color in " the area contained within these paths.
What should I look at?
You need to use the CGContextFillPath API. You should be careful of how you define the path, though:
Start by calling CGContextMoveToPoint on the initial point
Proceed by drawing all segments except the closing one with CGContextAddLineToPoint
Close the path with CGContextClosePath. Do not add line to point on the final segment.
The call of CGContextFillPath will produce a path colored with the fill color that you have previously set.
Here is an example:
CGPoint pt0 = startPoint = [[secondDotsArray objectAtIndex:0] CGPointValue];
CGContextMoveToPoint(context, pt0.x, pt0.y);
for (int i = 1 ; i < noOfDots ; i++) {
CGPoint next = [[secondDotsArray objectAtIndex:i] CGPointValue];
CGContextAddLineToPoint(context, next.x, next.y);
}
CGContextClosePath(context);
CGContextFillPath(context);

CoreText manual line breaking: How to position the text, vertical alignment?

I'm trying to create a custom label subclass that draws rich text and in which I can set different parameters. One of the most huge request I have is about line breaking. In UILabel is fixed and sometimes it doesn't fit the graphics requirements.
Thus, helping myself with a little snippet on Apple site, I've started writing my own classes, and it works (somehow), but I'm having one problems:
If the line is only one, the text is not aligned vertically, it always starts a the top left of the screen
Here is the code I've written so far:
- (void)drawRect:(CGRect)rect
{
if (_text) {
if (!_attribstring) {
[self createAttributedString];
}
if (self.lineBreakValue == 0) {
self.lineBreakValue = 9;
}
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextTranslateCTM(ctx, 0, self.bounds.size.height);
CGContextScaleCTM(ctx, 1.0, -1.0);
CGContextSetShouldSmoothFonts(ctx, YES);
CGContextSetShouldAntialias(ctx, YES);
CGRect textRect = CGRectMake(0, 0, self.bounds.size.width, self.bounds.size.height);
//Manual Line braking
BOOL shouldDrawAnotherLine = YES;
double width = textRect.size.width;
CGPoint textPosition = CGPointMake(textRect.origin.x, textRect.origin.y+textRect.size.height-self.lineBreakValue);
;
// Initialize those variables.
// Create a typesetter using the attributed string.
CTTypesetterRef typesetter = CTTypesetterCreateWithAttributedString((__bridge CFAttributedStringRef ) self.attribstring);
// Find a break for line from the beginning of the string to the given width.
CFIndex start = 0;
while (shouldDrawAnotherLine) {
CFIndex count = CTTypesetterSuggestLineBreak(typesetter, start, width);
// Use the returned character count (to the break) to create the line.
CTLineRef line = CTTypesetterCreateLine(typesetter, CFRangeMake(start, count));
// Get the offset needed to center the line.
float flush = 0.5; // centered
double penOffset = CTLineGetPenOffsetForFlush(line, flush, width);
// Move the given text drawing position by the calculated offset and draw the line.
CGContextSetTextPosition(ctx, textPosition.x + penOffset, textPosition.y);
CTLineDraw(line, ctx);
if (line!=NULL) {
CFRelease(line);
}
// Move the index beyond the line break.
if (start + count >= [self.attribstring.string length]) {
shouldDrawAnotherLine = NO;
continue;
}
start += count;
textPosition.y-=self.lineBreakValue;
}
if (typesetter!=NULL) {
CFRelease(typesetter);
}
}
}
Can someone point me to the right direction?
Regards,
Andrea
For laying out text vertically aligned, you need to know the total height of the lines, and the y position of the first line would be:
(self.bounds.size.height - (totalLineHeight)) / 2 - font.ascent;
So you need 2 loops in your code, one for calculating the total height of the lines(you can also save char count of each line for later use in another loop for drawing), another certainly for drawing the lines starting from the y position calculated using the above formula.
Note: Height of each line can be the font size, you can also add line spacing between lines, all you need to ensure is that being consistent in calculating the line height and drawing those lines in regards of the y position.

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)

How to draw a solid circle with cocos2d for iPhone

Is it possible to draw a filled circle with cocos2d ?
An outlined circle can be done using the drawCircle() function, but is there a way to fill it in a certain color? Perhaps by using pure OpenGL?
In DrawingPrimitives.m, change this in drawCricle:
glDrawArrays(GL_LINE_STRIP, 0, segs+additionalSegment);
to:
glDrawArrays(GL_TRIANGLE_FAN, 0, segs+additionalSegment);
You can read more about opengl primitives here:
http://www.informit.com/articles/article.aspx?p=461848
Here's a slight modification of ccDrawCircle() that lets you draw any slice of a circle. Stick this in CCDrawingPrimitives.m and also add the method header information to CCDrawingPrimitives.h:
Parameters: a: starting angle in radians, d: delta or change in angle in radians (use 2*M_PI for a complete circle)
Changes are commented
void ccDrawFilledCircle( CGPoint center, float r, float a, float d, NSUInteger totalSegs)
{
int additionalSegment = 2;
const float coef = 2.0f * (float)M_PI/totalSegs;
NSUInteger segs = d / coef;
segs++; //Rather draw over than not draw enough
if (d == 0) return;
GLfloat *vertices = calloc( sizeof(GLfloat)*2*(segs+2), 1);
if( ! vertices )
return;
for(NSUInteger i=0;i<=segs;i++)
{
float rads = i*coef;
GLfloat j = r * cosf(rads + a) + center.x;
GLfloat k = r * sinf(rads + a) + center.y;
//Leave first 2 spots for origin
vertices[2+ i*2] = j * CC_CONTENT_SCALE_FACTOR();
vertices[2+ i*2+1] =k * CC_CONTENT_SCALE_FACTOR();
}
//Put origin vertices into first 2 spots
vertices[0] = center.x * CC_CONTENT_SCALE_FACTOR();
vertices[1] = center.y * CC_CONTENT_SCALE_FACTOR();
// Default GL states: GL_TEXTURE_2D, GL_VERTEX_ARRAY, GL_COLOR_ARRAY, GL_TEXTURE_COORD_ARRAY
// Needed states: GL_VERTEX_ARRAY,
// Unneeded states: GL_TEXTURE_2D, GL_TEXTURE_COORD_ARRAY, GL_COLOR_ARRAY
glDisable(GL_TEXTURE_2D);
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
glVertexPointer(2, GL_FLOAT, 0, vertices);
//Change to fan
glDrawArrays(GL_TRIANGLE_FAN, 0, segs+additionalSegment);
// restore default state
glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glEnable(GL_TEXTURE_2D);
free( vertices );
}
Look into:
CGContextAddArc
CGContextFillPath
These will allow you to fill a circle without needing OpenGL
I also wonder this, but haven't really accomplished doing it. I tried using CGContext stuff that Grouchal tipped above, but I can't get it to draw anything on the screen. This is what I've tried:
-(void) draw
{
[self makestuff:UIGraphicsGetCurrentContext()];
}
-(void)makestuff:(CGContextRef)context
{
// Drawing lines with a white stroke color
CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0);
// Draw them with a 2.0 stroke width so they are a bit more visible.
CGContextSetLineWidth(context, 2.0);
// Draw a single line from left to right
CGContextMoveToPoint(context, 10.0, 30.0);
CGContextAddLineToPoint(context, 310.0, 30.0);
CGContextStrokePath(context);
// Draw a connected sequence of line segments
CGPoint addLines[] =
{
CGPointMake(10.0, 90.0),
CGPointMake(70.0, 60.0),
CGPointMake(130.0, 90.0),
CGPointMake(190.0, 60.0),
CGPointMake(250.0, 90.0),
CGPointMake(310.0, 60.0),
};
// Bulk call to add lines to the current path.
// Equivalent to MoveToPoint(points[0]); for(i=1; i<count; ++i) AddLineToPoint(points[i]);
CGContextAddLines(context, addLines, sizeof(addLines)/sizeof(addLines[0]));
CGContextStrokePath(context);
// Draw a series of line segments. Each pair of points is a segment
CGPoint strokeSegments[] =
{
CGPointMake(10.0, 150.0),
CGPointMake(70.0, 120.0),
CGPointMake(130.0, 150.0),
CGPointMake(190.0, 120.0),
CGPointMake(250.0, 150.0),
CGPointMake(310.0, 120.0),
};
// Bulk call to stroke a sequence of line segments.
// Equivalent to for(i=0; i<count; i+=2) { MoveToPoint(point[i]); AddLineToPoint(point[i+1]); StrokePath(); }
CGContextStrokeLineSegments(context, strokeSegments, sizeof(strokeSegments)/sizeof(strokeSegments[0]));
}
These methods are defined in a cocos node class, and the makestuff method I borrowed from a code example...
NOTE:
I'm trying to draw any shape or path and fill it. I know that the code above only draws lines, but I didn't wanna continue until I got it working.
EDIT:
This is probably a crappy solution, but I think this would at least work.
Each CocosNode has a texture (Texture2D *). Texture2D class can be initialized from an UIImage. UIImage can be initialized from a CGImageRef. It is possible to create a CGImageRef context for the quartz lib.
So, what you would do is:
Create the CGImageRef context for quartz
Draw into this image with quartz
Initialize an UIImage with this CGImageRef
Make a Texture2D that is initialized with that image
Set the texture of a CocosNode to that Texture2D instance
Question is if this would be fast enough to do. I would prefer if you could sort of get a CGImageRef from the CocosNode directly and draw into it instead of going through all these steps, but I haven't found a way to do that yet (and I'm kind of a noob at this so it's hard to actually get somewhere at all).
There is a new function in cocos2d CCDrawingPrimitives called ccDrawSolidCircle(CGPoint center, float r, NSUInteger segs). For those looking at this now, use this method instead, then you don't have to mess with the cocos2d code, just import CCDrawingPrimitives.h
I used this way below.
glLineWidth(2);
for(int i=0;i<50;i++){
ccDrawCircle( ccp(s.width/2, s.height/2), i,0, 50, NO);
}
I made multiple circle with for loop and looks like a filled circle.