Размытый скриншот представления, рисуемого UIBezierPath

Я рисую свой график, используя UIBezierPathmethods и основной текст. Я использую метод addQuadCurveToPoint:controlPoint: для рисования кривых на графике. Я также использую CATiledLayer для визуализации графика с большим набором данных по оси X. Я рисую весь свой график в контексте изображения, а в методе drawrect: моего представления я рисую это изображение во всем своем представлении. Ниже приведен мой код.

- (void)drawImage{

        UIGraphicsBeginImageContextWithOptions(self.frame.size, NO, 0.0);

        // Draw Curves 
        [self drawDiagonal];

        UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext();
        [screenshot retain];
        UIGraphicsEndImageContext();
}
- (void)drawRect:(CGRect)rect{

    NSLog(@"Draw iN rect with Bounds: %@",NSStringFromCGRect(rect));
    [screenshot drawInRect:self.frame];
}

Однако на скриншоте кривые, проведенные между двумя точками, не являются гладкими. Я также установил Render with Antialiasing на YES в своем информационном списке. Пожалуйста, смотрите скриншот. введите здесь описание изображения


person Abid Hussain    schedule 24.12.2012    source источник


Ответы (1)


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

Чтобы проиллюстрировать это, я обычно рисую кривые UIBezierPath так, чтобы иметь массив точек на кривой, угол, который должна принимать кривая в этой точке, а затем "вес" контрольных точек addCurveToPoint (т.е. контрольные точки должны быть). Таким образом, я использую эти параметры, чтобы указать вторую контрольную точку UIBezierPath и первую контрольную точку следующего сегмента UIBezierPath. Так, например:

@interface BezierPoint : NSObject
@property CGPoint point;
@property CGFloat angle;
@property CGFloat weight;
@end

@implementation BezierPoint

- (id)initWithPoint:(CGPoint)point angle:(CGFloat)angle weight:(CGFloat)weight
{
    self = [super init];

    if (self)
    {
        self.point  = point;
        self.angle  = angle;
        self.weight = weight;
    }

    return self;
}

@end

И затем, пример того, как я это использую:

- (void)loadBezierPointsArray
{
    // clearly, you'd do whatever is appropriate for your chart.
    // this is just a unclosed loop. But it illustrates the idea.

    CGPoint startPoint = CGPointMake(self.view.frame.size.width / 2.0, 50);

    _bezierPoints = [NSMutableArray arrayWithObjects:
            [[BezierPoint alloc] initWithPoint:CGPointMake(startPoint.x, startPoint.y) 
                                         angle:M_PI_2 * 0.05
                                        weight:100.0 / 1.7],
            [[BezierPoint alloc] initWithPoint:CGPointMake(startPoint.x + 100.0, startPoint.y + 70.0) 
                                         angle:M_PI_2
                                        weight:70.0 / 1.7],
            [[BezierPoint alloc] initWithPoint:CGPointMake(startPoint.x, startPoint.y + 140.0)
                                         angle:M_PI
                                        weight:100.0 / 1.7],
            [[BezierPoint alloc] initWithPoint:CGPointMake(startPoint.x - 100.0, startPoint.y + 70.0)
                                         angle:M_PI_2 * 3.0
                                        weight:70.0 / 1.7],
            [[BezierPoint alloc] initWithPoint:CGPointMake(startPoint.x + 10.0, startPoint.y + 10)
                                         angle:0.0
                                        weight:100.0 / 1.7],
            nil];
}

- (CGPoint)calculateForwardControlPoint:(NSUInteger)index
{
    BezierPoint *bezierPoint = _bezierPoints[index];

    return CGPointMake(bezierPoint.point.x + cosf(bezierPoint.angle) * bezierPoint.weight,  
                       bezierPoint.point.y + sinf(bezierPoint.angle) * bezierPoint.weight);
}

- (CGPoint)calculateReverseControlPoint:(NSUInteger)index
{
    BezierPoint *bezierPoint = _bezierPoints[index];

    return CGPointMake(bezierPoint.point.x - cosf(bezierPoint.angle) * bezierPoint.weight,  
                       bezierPoint.point.y - sinf(bezierPoint.angle) * bezierPoint.weight);
}

- (UIBezierPath *)bezierPath
{
    UIBezierPath *path = [UIBezierPath bezierPath];
    BezierPoint *bezierPoint = _bezierPoints[0];

    [path moveToPoint:bezierPoint.point];

    for (NSInteger i = 1; i < [_bezierPoints count]; i++)
    {
        bezierPoint = _bezierPoints[i];

        [path addCurveToPoint:bezierPoint.point
                controlPoint1:[self calculateForwardControlPoint:i - 1] 
                controlPoint2:[self calculateReverseControlPoint:i]];
    }

    return path;
}

Когда я визуализирую это в UIImage (используя приведенный ниже код), я не вижу никакого смягчения изображения, но надо признать, что изображения не идентичны. (Я сравниваю изображение, созданное capture, с изображением, которое я сделал вручную, с помощью снимка экрана, одновременно нажав кнопки питания и «Домой» на моем физическом устройстве.)

Если вы видите некоторое смягчение, я бы предложил renderInContext (как показано ниже). Интересно, пишете ли вы изображение в формате JPG (с потерями). Может быть, попробуйте PNG, если вы использовали JPG.

- (void)drawBezier
{
    UIBezierPath *path = [self bezierPath];

    CAShapeLayer *oval = [[CAShapeLayer alloc] init];
    oval.path = path.CGPath;
    oval.strokeColor = [UIColor redColor].CGColor;
    oval.fillColor = [UIColor clearColor].CGColor;
    oval.lineWidth = 5.0;
    oval.strokeStart = 0.0;
    oval.strokeEnd = 1.0;
    [self.view.layer addSublayer:oval];
}

- (void)capture
{
    UIGraphicsBeginImageContextWithOptions(self.view.frame.size, NO, 0.0);
    CGContextRef context = UIGraphicsGetCurrentContext();
    [self.view.layer renderInContext:context];
    UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();

    // save the image

    NSData *data = UIImagePNGRepresentation(screenshot);
    NSString *documentsPath = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)[0];
    NSString *imagePath = [documentsPath stringByAppendingPathComponent:@"image.png"];
    [data writeToFile:imagePath atomically:YES];

    // send it to myself so I can look at the file

    NSURL *url = [NSURL fileURLWithPath:imagePath];
    UIActivityViewController *controller = [[UIActivityViewController alloc] initWithActivityItems:@[url]
                                                                         applicationActivities:nil];
    [self presentViewController:controller animated:YES completion:nil];
}
person Rob    schedule 24.12.2012
comment
Прежде всего, спасибо за ваш ответ. Я обязательно попробую это. Если вы хотите увидеть, как я рисую кривые, вы можете увидеть этот вопрос, опубликованный мной, и ответ на него. stackoverflow.com/questions/13719143/ Фактически вы дали идеальный ответ на этот вопрос. Однако моя проблема больше связана с острыми краями изогнутых линий, а не с плавными кривыми. Края кривых гладкие, если я рисую их прямо в поле зрения. Отрисовка всего представления с помощью CATiledLayer вызывала некоторые проблемы, поэтому я решил отрисовать весь график в виде изображения. - person Abid Hussain; 24.12.2012
comment
@AbidHussain Мой код или, что более важно, наблюдение наклонов контрольных точек и конечных/начальных точек, касается того, что я бы назвал разрывом первой производной, т. е. того факта, что кривая делает резкий поворот в точке. Если есть другая проблема, которую вы пытаетесь решить, дайте мне знать. Похоже, вы описываете какую-то проблему с рендерингом, но я не полностью понимаю вашу задачу. Я думал, вы просто пытаетесь решить проблему разрыва. Что касается рендеринга, я обычно просто создаю CAShapeLayer и устанавливаю его путь как мой UIBezierPath. - person Rob; 24.12.2012
comment
да, на самом деле я сталкиваюсь с размытым рендерингом кривых, когда делаю скриншот моего рисунка внутри UIGraphicsBeginImageContextWithOptions. С рендерингом все в порядке, если я рисую его непосредственно в UIView вместо того, чтобы рисовать UIImage моего рисунка в UIView. - person Abid Hussain; 24.12.2012
comment
@AbidHussain Вы пробовали renderInContext, как в моем обновленном ответе? Кроме того, вы сохраняете как PNG или JPEG. Бывший, вероятно, лучше, так как он без потерь. Я не вижу размытия, когда использую технику в своем ответе. - person Rob; 24.12.2012