CALayer drawInContext не может рисовать линии 1px на сетчатых дисплеях

Я хочу нарисовать двойную цветную линию с линиями 1px из двух разных цветов. У меня много кода, поэтому я пытаюсь объяснить его.

Если я использую метод uiview drawRect, он отлично работает, когда я выключаю сглаживание, но когда я использую слой drawLayerInContext, он либо отображает одну простую линию, либо две простые линии 2px с сглаживанием или двумя полупрозрачными линиями 2px с антилечением.

Мне удалось получить аналогичное поведение, чем метод UIView drawRect, создав изображение с настраиваемым контекстом, где я могу указать масштаб:

UIGraphicsBeginImageContextWithOptions([self bounds].size, NO, 0.f); // 0.f for scale means "scale for device main screen".

Я просто хотел бы знать, почему я не получаю такое же поведение в drawInContext, и если бы был способ получить подобное поведение.

Вот код, который рисует двухцветную строку:

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();
}

С помощью метода drawRect UIView я получаю следующее:

| 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 |

В CALayer с drawInContext я получаю эти результаты

| 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 |

Используя мой пользовательский контекст, я получаю следующее:

| 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 |

Код для drawRect реализации:

- (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);
}

Код для drawInContext реализации:

-(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);
}

Код для реализации пользовательского контекста в методе CALayer display:

-(void)display{

    if ([UIScreen instancesRespondToSelector:@selector(scale)]) {
        UIGraphicsBeginImageContextWithOptions([self bounds].size, NO, 0.f); // 0.f for scale means "scale for device 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;
}

Ответ 1

НЕ ИСПОЛЬЗУЙТЕ 0,5 в качестве ширины.

замените 0.5 на 1.0 / [UIScreen mainScreen].scale

Когда вы нарисуете слой, вы можете получить линию ширины 1 пикселя.

Ответ 2

Я бы предложил вместо рисования строк просто нарисовать заполненные прямоугольники нужного размера. Одной из сложнейших частей с разными разрешениями являются местоположения начальной и конечной точек линий. Но рисовать прямоугольники намного проще.

Координаты для прямоугольников всегда четные, поскольку вы нацеливаете край, а не центр пикселя.

Ответ 3

Нарисуйте и заполните прямоугольники вместо строк. Координаты для прямоугольников всегда должны быть на краю пикселя, а не центра.

Ответ 4

Лучший способ, который я нашел, чтобы получить четкие/четкие линии 1px на сетчатке/не-сетчатке и поддерживать их как 1px, даже если вы увеличиваете масштаб, это писать шейдер. В моем случае я использовал OpenGL, но я считаю, что вы можете сделать это с помощью Metal for CALayers. Основной процесс состоит в том, чтобы нарисовать прямоугольник нулевой высоты (или нулевую ширину, если линия вертикальна), а затем в шейдере надавите эти точки наружу на нужное количество пикселей.

У меня появилась идея отсюда: https://www.mapbox.com/blog/drawing-antialiased-lines/