Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CALayer drawInContext can't draw 1px lines on retina displays

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;
}
like image 240
Knshiro Avatar asked May 02 '13 06:05

Knshiro


1 Answers

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.

like image 65
aelam Avatar answered Nov 05 '22 13:11

aelam