Drawing a very thin line with CGContextAddLineToPoint and CGContextSetLineWidth
Asked Answered
E

2

15

I want to draw a very thin hairline width of a line in my UIView's drawRect method. The line I see that has a value 0.5 for CGContextSetLineWidth doesn't match the same 1.0 width value that is used to draw a border CALayer.

You can see the difference between the two - the red line (width = 1) is a lot thinner than the purple/blue line (width = 0.5).

Border drawn with CALayer

Here's how I am drawing my pseudo 1.0 width horizontal line:

CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(ctx, [UIColor blueColor].CGColor);
CGContextSetLineWidth(ctx, 0.5); // I expected a very thin line

CGContextMoveToPoint(ctx, 0, y);
CGContextAddLineToPoint(ctx, self.bounds.size.width, y);

CGContextStrokePath(ctx);

Here's a border for the same view, this time using 1.0 border width:

UIView *myView = (UIView *)self;
CALayer *layer = myView.layer;
layer.borderColor = [UIColor redColor].CGColor;
layer.borderWidth = 1.0;

What do I need to do differently to draw my own custom line that's the same width as the CALayer version?

Ex answered 14/10, 2012 at 2:21 Comment(0)
E
38

When you stroke a path, the stroke straddles the path. To say it another way, the path lies along the center of the stroke.

If the path runs along the edge between two pixels, then the stroke will (partially) cover the pixels on both sides of that edge. With a line width of 0.5, a horizontal stroke will extend 0.25 points into the pixel above the path, and 0.25 points into the pixel below the path.

You need to move your path so it doesn't run along the edge of the pixels:

CGFloat lineWidth = 0.5f;
CGContextSetLineWidth(ctx, lineWidth);

// Move the path down by half of the line width so it doesn't straddle pixels.
CGContextMoveToPoint(ctx, 0, y + lineWidth * 0.5f);
CGContextAddLineToPoint(ctx, self.bounds.size.width, y + lineWidth * 0.5f);

But since you're just drawing a horizontal line, it's simpler to use CGContextFillRect:

CGContextSetFillColorWithColor(ctx, [UIColor blueColor].CGColor);
CGContextFillRect(ctx, CGRectMake(0, y, self.bounds.size.width, 0.5f));
Execution answered 14/10, 2012 at 3:8 Comment(2)
It's a kind of magic! It works, but could you explain why i should move the path down by half of the line width or give some link?Wassyngton
But he just explained so clearly, in 6 clear lines of text… Avoid straddling the pixel boundaries when drawing thin lines. The Antialiasing of the drawing mechanism create "smeared" line which visually looks thicker than what you expected, but they're not.Corpuscle
B
16

You need to turn off antialiasing to get a thin line when not drawn on an integral.

CGContextSetShouldAntialias(ctx, NO)
Behold answered 19/6, 2013 at 20:10 Comment(5)
Glad to be of help, I have often overlooked antialias myself.Behold
I was working this on an iOS7 app, 1 pixel line in retina, this command had no affect. YMMV.Etna
this is better than moving the path so that it doesn't straddle pixels. thanks.Smug
I'd manually move away from the pixel boundary, and avoid shutting off antiAliasing, because most drawing in the view looks much better when antiAliasing is on All diagonal lines and curves will look jaggy after calling the above command.Corpuscle
You only turn off antialias for your own drawing code. It goes without saying that the drawing context should be saved and popped for good coding habits.Behold

© 2022 - 2024 — McMap. All rights reserved.