Reputation: 231
I want to draw double colored line with 1px lines of 2 different colors. I have a lot of code so I'm putting it after trying to explain.
If I use uiview drawRect
method it works fine when I switch off antialiasing but when I use layer's drawLayerInContext
it either displays one plain line, or 2 plain line lines of 2px with antialiasing off or two half transparent lines of 2px with antialising on.
I managed to get a similar behavior than UIView's drawRect method by creating an image with a custom context where I can specify a scale:
UIGraphicsBeginImageContextWithOptions([self bounds].size, NO, 0.f); // 0.f for scale means "scale for device's main screen".
I would just like to know why I don't get the same behavior in drawInContext
and if there was a way to get similar behavior.
Here is the code that draws the double color line:
void DRAW_DOUBLE_LINE(CGContextRef ctx, CGPoint startPoint, CGPoint endPoint, UIColor* topColor, UIColor* bottomColor)
{
UIGraphicsPushContext(ctx);
UIBezierPath *topLine = [[UIBezierPath alloc] init];
CGPoint topLineStartPoint = startPoint;
CGPoint topLineEndPoint = endPoint;
[topLine moveToPoint:topLineStartPoint];
[topLine addLineToPoint:topLineEndPoint];
[topColor setStroke];
topLine.lineWidth = 0.5;
[topLine stroke];
UIBezierPath *bottomLine = [[UIBezierPath alloc] init];
CGPoint bottomLineStartPoint = topLineStartPoint;
bottomLineStartPoint.y +=0.5;
CGPoint bottomLineEndPoint = topLineEndPoint;
bottomLineEndPoint.y +=0.5;
[bottomLine moveToPoint:bottomLineStartPoint];
[bottomLine addLineToPoint:bottomLineEndPoint];
[bottomColor setStroke];
bottomLine.lineWidth = 0.5;
[bottomLine stroke];
UIGraphicsPopContext();
}
With the drawRect
method of UIView
I obtain this:
| Points y coordinate | Antialiasing | Result | | ------------------- | ------------ | ------------------------------- | | 5 | NO | 2 lines of 1 px: Bingo! | | 5.25 | NO | 2 lines of 1 px: Bingo! | | 5.5 | NO | 2 lines of 1 px: Bingo! | | 5 | YES | 3 half transparent lines of 1px | | 5.25 | YES | 2 lines of 1 px: Bingo! | | 5.5 | YES | 3 half transparent lines of 1px |
In a CALayer with drawInContext I get these results
| Points y coordinate | Antialiasing | Result | | ------------------- | ------------ | ------------------------------- | | 5 | NO | 2 lines of 2 px | | 5.25 | NO | 1 line of 2 px | | 5.5 | NO | 1 line of 2 px | | 5 | YES | 2 half transparent lines of 2px | | 5.25 | YES | 1 half transparent line of 2px | | 5.5 | YES | 2 half transparent lines of 2px |
Using my custom context I get this:
| Points y coordinate | Antialiasing | Result | | ------------------- | ------------ | ------------------------------- | | 5 | NO | 2 lines of 1 px: Bingo! | | 5.25 | NO | 2 lines of 1 px: Bingo! | | 5.5 | NO | 2 lines of 1 px: Bingo! | | 5 | YES | 3 half transparent lines of 1px | | 5.25 | YES | 2 lines of 1 px: Bingo! | | 5.5 | YES | 3 half transparent lines of 1px |
Code for drawRect
implementation:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGPoint startPoint = CGPointMake(0, 5);
CGPoint endPoint = CGPointMake(CGRectGetMaxX(self.bounds),5);
UIColor* topLineColor = [UIColor whiteColor];
UIColor* bottomLineColor = [UIColor blackColor];
DRAW_DOUBLE_LINE(context, startPoint, endPoint, topLineColor, bottomLineColor);
}
Code for drawInContext
implementation:
-(void)drawInContext:(CGContextRef)ctx{
CGPoint startPoint = CGPointMake(0, 5);
CGPoint endPoint = CGPointMake(CGRectGetMaxX(self.bounds),5);
UIColor* topLineColor = [UIColor whiteColor];
UIColor* bottomLineColor = [UIColor blackColor];
DRAW_DOUBLE_LINE(ctx, startPoint, endPoint, topLineColor, bottomLineColor);
}
Code for custom context implementation in CALayer's display
method:
-(void)display{
if ([UIScreen instancesRespondToSelector:@selector(scale)]) {
UIGraphicsBeginImageContextWithOptions([self bounds].size, NO, 0.f); // 0.f for scale means "scale for device's main screen".
} else {
UIGraphicsBeginImageContext([self bounds].size);
}
CGContextRef context = UIGraphicsGetCurrentContext();
CGPoint startPoint = CGPointMake(0, 5.25);
CGPoint endPoint = CGPointMake(CGRectGetMaxX(self.bounds),5.25);
UIColor* topLineColor = [UIColor whiteColor];
UIColor* bottomLineColor = [UIColor blackColor];
DRAW_DOUBLE_LINE(ctx, startPoint, endPoint, topLineColor, bottomLineColor);
UIImage *coloredImg = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
self.contents = (id)coloredImg.CGImage;
}
Upvotes: 12
Views: 1655
Reputation: 672
The best way I've found to get precise/crisp looking 1px lines on both retina/non-retina and maintain them as 1px even if you zoom in is to write a shader. In my case I was using OpenGL, but I believe you could do this with Metal for CALayers. The basic process is to draw a rectangle of zero height (or zero width if you line is vertical) and then in the shader push those points outward by the desired number of pixels.
I got the idea from here: https://www.mapbox.com/blog/drawing-antialiased-lines/
Upvotes: 0
Reputation: 2916
DON'T USE 0.5 as the width.
replace 0.5
with 1.0 / [UIScreen mainScreen].scale
When you draw on layer you can get 1 pixel width line.
Upvotes: 1
Reputation:
Draw and fill rectangles instead of lines. The coordinates for rectangles should always be at the edge of a pixel and not the center.
Upvotes: 0
Reputation: 3095
I would suggest instead of drawing lines, just draw filled rectangles of the desired size. One of the tricky parts with different resolutions are the locations of the start and end points of lines. But drawing rectangles is much easier.
The coordinates for rectangles are always even, because you target the edge and not the center of a pixel.
Upvotes: 0