I'm having a problem in Core Graphics where two shapes that are supposed to fit together are showing seams between them when their sides are diagonal. This is causing problems. I can 'fix' this by overlapping the shapes. This works for simple stuff, but when the shape is more complex things start to fall apart.
Here is a simple example:
[[UIColor blackColor] setFill];
[path fill]; //fills the entire background black
UIBezierPath *path1 = [UIBezierPath bezierPath];
[path1 moveToPoint:CGPointMake(0.0f, 0.0f)];
[path1 addLineToPoint:CGPointMake(100, 0.0f)];
[path1 addLineToPoint:CGPointMake(50, rect.size.height)];
[path1 addLineToPoint:CGPointMake(0, rect.size.height)];
[path1 closePath];
UIBezierPath *path2 = [UIBezierPath bezierPath];
[path2 moveToPoint:CGPointMake(100, 0)];
[path2 addLineToPoint:CGPointMake(rect.size.width, 0)];
[path2 addLineToPoint:CGPointMake(rect.size.width, rect.size.height)];
[path2 addLineToPoint:CGPointMake(50, rect.size.height)];
[path2 closePath];
[[UIColor lightGrayColor] setFill];
[path1 fill];
[path2 fill];
This produces a seam that looks something like this:
This is exceptionally problematic because this is occurring in a CGPathApply call back function I'm using to process shapes (I divide the shapes up, do some processing and put them back together again). Because of this, it's very difficult to determine how those shapes should be shifted in such a way as to remove the seams (by overlapping) without distorting the shape. Especially since some of these shapes are rather complex (not simple rects).
I did discover that turning off antialiasing (CGContextSetShouldAntialias(context, NO);) does remove the seems but things just get ugly if I do that, so it's not really an option.
Any ideas?
Related
I'm trying to draw a power function curve in a 300x300 pixel rectangle using NSBezierPath as follows:
-(void)drawPowerCurve:(float)power points:(int)numbPoints{
NSBezierPath * path = [NSBezierPath bezierPath];
[path setLineWidth: 1.0];
[[NSColor colorWithCalibratedRed:0.0 green:0.0 blue:1.0 alpha:1.0] set];
NSPoint borderOrigin = {35.5,15.5};
NSPoint endPoint;
[path moveToPoint:borderOrigin];
for(int i = 0; i < numbPoints; i++){
endPoint.x = borderOrigin.x + (300.0/numbPoints)*(i+1);
endPoint.y = borderOrigin.y + 300.0*pow(((i+1)/(float)numbPoints), power);
[path lineToPoint:endPoint];
[path stroke];
[path moveToPoint:endPoint];
}
}
However, the curve thins out at the top end compared to the bottom end.
For example power = 1.8 and numbPoints = 50.
Also the curve doesn't look as smooth as for example curves shown in Apple's ColorSync Utility. Of course I don't know how they are drawing the curves in ColorSync. Any ideas on how to improve the look of these curves (particularly getting rid of the thining out).
Edit -- Here is a screenshot:
Move the stroke out of the loop so you only draw the curve once instead of numbPoints times as it grows. Also remove the redundant moveToPoint in the loop, lineToPoint leaves the current point at the end of the added segment.
I'm drawing some simple bezier paths, but I'm finding it impossible to remove the spikes created when the angle between line segments is small:
(Note: The circle is from a separate drawing operation, but I'm trying to make sure the line does not spike past the circle...).
I've tried all kinds of variations of lineCapStyle and lineJoinStyle but nothing seems to work.
In addition to what is shown below I have tried using a Miter Join with 'setMiterLimit'.
Here's my line drawing code snip:
CAShapeLayer *myShapeLayer=[CAShapeLayer layer];
UIBezierPath *myPath=[UIBezierPath bezierPath];
[myPath moveToPoint:tmpPoint];
[myPath addLineToPoint:tmpPoint];
[myPath setLineCapStyle:kCGLineCapRound];
[myPath setLineJoinStyle:kCGLineJoinRound];
myShapeLayer.path=[myPath CGPath];
myShapeLayer.strokeColor = [[UIColor yellowColor] CGColor];
myShapeLayer.fillColor = [[UIColor clearColor] CGColor];
myShapeLayer.lineWidth = 3.0;
Just in case - here's the Miter code I've used, varying the value rom 0.0 to 100.0 - all with no effect:
[myPath setLineCapStyle:kCGLineCapRound];
[myPath setLineJoinStyle:kCGLineJoinMiter];
[myPath setMiterLimit:1.0];
You should be setting lineJoin on the shape layer instead of the path:
myShapeLayer.lineJoin = kCALineJoinRound;
The confusion comes from the fact that UIBezierPath has the ability to draw the path (by calling fill and stroke on the path). The configuration of line joins and line caps on the path is only affecting this drawing.
However, since you are drawing the path using a CAShapeLayer, the configurations of both line joins and line caps should be done on the shape layer.
The short story is that I would like the bounds (I think I mean bounds instead of frame) of a NSBezierPath to fill a view. Something like this:
To generate the above image I scaled/translated each point in my created path using the information from Covert latitude/longitude point to a pixels (x,y) on mercator projection. The problem is that this isn't scalable (I will be adding many more paths) and I want to easily add pan/zoom functionality to my view. Additionally, I want the stroke to remain the same regardless of scale (i.e. no fat boundaries when I zoom).
I think I want to generate a reusable path in some arbitrary reference frame (e.g. longitude and modified latitude) instead of generating a new path every time the window changes. Then I can translate/scale my view's coordinate system to fill the view with the path.
So I used Apple's geometry guide to to modify the view's frame. I got the translation right but scaling failed.
[self setBoundsOrigin:self.path.bounds.origin];
[self scaleUnitSquareToSize:NSMakeSize(1.5, 1.5)];
Then I tried a coordinate system transformation in my drawRect: method only to end up with a similar result.
NSAffineTransform* xform = [NSAffineTransform transform];
[xform translateXBy:(-self.path.bounds.origin.x) yBy:(-self.path.bounds.origin.y)];
[xform scaleXBy:1.5 yBy:1.5];
[xform concat];
Finally I tried manually setting the view bounds in drawRect: but the result was ugly and very slow!
I know I can also transform the NSBezierPath object and I think that would work, but I'd rather transform the view once instead of looping through and transforming each path every update. I think there's about three lines of code I'm missing that will do exactly what I'm looking for.
Edit:
Here's the drawRect: method I'm using:
- (void)drawRect:(NSRect)dirtyRect
{
// NSAffineTransform* xform = [NSAffineTransform transform];
// [xform translateXBy:-self.path.bounds.origin.x yBy:-self.path.bounds.origin.y];
// [xform scaleXBy:1.5 yBy:1.5];
// [xform concat];
[self drawBoundaries];
NSRect bounds = [self bounds];
[[NSColor blackColor] set];
[NSBezierPath fillRect:bounds];
// Draw the path in white
[[NSColor whiteColor] set];
[self.path stroke];
[[NSColor redColor] set];
[NSBezierPath strokeRect:self.path.bounds];
NSLog(#"path origin %f x %f",self.path.bounds.origin.x, self.path.bounds.origin.y);
NSLog(#"path bounds %f x %f",self.path.bounds.size.width, self.path.bounds.size.height);
}
I was able to get it to work using two transformations. I was trying to avoid this to reduce complexity and computation when I have many paths to transform and a window that zooms/pans.
- (void)transformPath:(NSBezierPath *)path
{
NSAffineTransform *translateTransform = [NSAffineTransform transform];
NSAffineTransform *scaleTransform = [NSAffineTransform transform];
[translateTransform translateXBy:(-self.path.bounds.origin.x)
yBy:(-self.path.bounds.origin.y)];
float scale = MIN(self.bounds.size.width / self.path.bounds.size.width,
self.bounds.size.height / self.path.bounds.size.height);
[scaleTransform scaleBy:scale];
[path transformUsingAffineTransform: translateTransform];
[path transformUsingAffineTransform: scaleTransform];
}
I have to draw lines few times. I do this now:
- (void)drawRect:(NSRect)dirtyRect
{
[NSGraphicsContext saveGraphicsState];
NSBezierPath *path = [NSBezierPath bezierPath];
[path moveToPoint:point];
[[NSColor blackColor] set];
[path lineToPoint:point2];
[path stroke];
[NSGraphicsContext restoreGraphicsState];
}
And i call this from other class many time with other parameters:
[workspace setPoint1:someValue setPoint2:someOtherValue];
[workspace setNeedsDisplay:YES];
What i need?
I call this few times with changing parameters someValue and someOtherValue and I have to draw all lines and I want to see it. Now i see only last path. Where is the problem? How can i do this correctly?
Thank you.
If you want to draw more than one line, you'll need to... well... draw more than one line.
You'll need to come up with some way of storing all of the line segments that you want to draw, not just the most recent one. (Use NSMutableArray for this.) Once you've got that, you'll need to loop through that array and draw each line.
i defined this to convert degrees to radians
#define DEGREES_TO_RADIANS(degrees) ((3.14159 * degrees)/180)
then i added this code snippet to my viewDidLoad. I am trying to create an arc here.
UIBezierPath *innerPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(150, 150) radius:75 startAngle:0 endAngle:DEGREES_TO_RADIANS(135) clockwise:YES];
[[UIColor blackColor] setStroke];
[[UIColor redColor] setFill];
innerPath.lineWidth = 5;
[innerPath fill];
[innerPath stroke];
However the arc does not appear when I run my app in the simulator. Where am I going wrong?
First, use M_PI instead of 3.14159.
Second, you need to do your drawing in a view's drawRect: method. Read about the UIKit drawing model for more details.