Почему UIBezierPath быстрее, чем основной графический путь?


я играл с рисованием путей, и я заметил, что, по крайней мере, в некоторых случаях UIBezierPath превосходит то, что я думал, будет основным графическим эквивалентом. Элемент -drawRect: метод ниже создает два пути: один UIBezierPath и один CGPath. Пути идентичны, за исключением их расположения, но поглаживание CGPath занимает примерно в два раза больше времени, чем поглаживание UIBezierPath.

- (void)drawRect:(CGRect)rect
{
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    // Create the two paths, cgpath and uipath.
    CGMutablePathRef cgpath = CGPathCreateMutable();
    CGPathMoveToPoint(cgpath, NULL, 0, 100);

    UIBezierPath *uipath = [[UIBezierPath alloc] init];
    [uipath moveToPoint:CGPointMake(0, 200)];

    // Add 200 curve segments to each path.
    int iterations = 200;
    CGFloat cgBaseline = 100;
    CGFloat uiBaseline = 200;
    CGFloat xincrement = self.bounds.size.width / iterations;
    for (CGFloat x1 = 0, x2 = xincrement;
         x2 < self.bounds.size.width;
         x1 = x2, x2 += xincrement)
    {
        CGPathAddCurveToPoint(cgpath, NULL, x1, cgBaseline-50, x2, cgBaseline+50, x2, cgBaseline);
        [uipath addCurveToPoint:CGPointMake(x2, uiBaseline)
                  controlPoint1:CGPointMake(x1, uiBaseline-50)
                  controlPoint2:CGPointMake(x2, uiBaseline+50)];
    }
    [[UIColor blackColor] setStroke];
    CGContextAddPath(ctx, cgpath);

    // Stroke each path.
    [self strokeContext:ctx];
    [self strokeUIBezierPath:uipath];

    [uipath release];
    CGPathRelease(cgpath);
}

- (void)strokeContext:(CGContextRef)context
{
    CGContextStrokePath(context);
}

- (void)strokeUIBezierPath:(UIBezierPath*)path
{
    [path stroke];
}

оба пути используют CGContextStrokePath (), поэтому я создал отдельные методы для обводки каждого путь, чтобы я мог видеть время, используемое каждым путем в инструментах. Ниже приведены типичные результаты (перевернутое дерево вызовов); вы можете видеть, что -strokeContext: занимает 9,5 сек., в то время как -strokeUIBezierPath: занимает всего 5 сек.:

Running (Self)      Symbol Name
14638.0ms   88.2%               CGContextStrokePath
9587.0ms   57.8%                 -[QuartzTestView strokeContext:]
5051.0ms   30.4%                 -[UIBezierPath stroke]
5051.0ms   30.4%                  -[QuartzTestView strokeUIBezierPath:]

похоже, что UIBezierPath каким-то образом оптимизирует путь, который он создает, или я создаю cgpath наивным способом. Что я могу сделать, чтобы ускорить мой рисунок CGPath?

1 84

1 ответ:

вы правы в том, что UIBezierPath это просто objective-c обертка для основной графики, и поэтому будет выполнять сравнительно. Разница (и причина вашей дельты производительности) - это ваш CGContext состояние при рисовании вашего CGPath непосредственно сильно отличается от этой установки по UIBezierPath. Если вы посмотрите на UIBezierPath, оно имеет параметры:

  • lineWidth,
  • lineJoinStyle,
  • lineCapStyle,
  • miterLimit и
  • flatness

при рассмотрении вызова (разборки) на [path stroke], вы заметите, что он настраивает текущий графический контекст на основе этих предыдущих значений перед выполнением CGContextStrokePath звонок. Если вы сделаете то же самое до рисования вашего CGPath, он будет выполнять то же самое:

- (void)drawRect:(CGRect)rect
{
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    // Create the two paths, cgpath and uipath.
    CGMutablePathRef cgpath = CGPathCreateMutable();
    CGPathMoveToPoint(cgpath, NULL, 0, 100);

    UIBezierPath *uipath = [[UIBezierPath alloc] init];
    [uipath moveToPoint:CGPointMake(0, 200)];

    // Add 200 curve segments to each path.
    int iterations = 80000;
    CGFloat cgBaseline = 100;
    CGFloat uiBaseline = 200;
    CGFloat xincrement = self.bounds.size.width / iterations;
    for (CGFloat x1 = 0, x2 = xincrement;
         x2 < self.bounds.size.width;
         x1 = x2, x2 += xincrement)
    {
        CGPathAddCurveToPoint(cgpath, NULL, x1, cgBaseline-50, x2, cgBaseline+50, x2, cgBaseline);
        [uipath addCurveToPoint:CGPointMake(x2, uiBaseline)
                  controlPoint1:CGPointMake(x1, uiBaseline-50)
                  controlPoint2:CGPointMake(x2, uiBaseline+50)];
    }
    [[UIColor blackColor] setStroke];
    CGContextAddPath(ctx, cgpath);

    // Stroke each path
    CGContextSaveGState(ctx); {
        // configure context the same as uipath
        CGContextSetLineWidth(ctx, uipath.lineWidth);
        CGContextSetLineJoin(ctx, uipath.lineJoinStyle);
        CGContextSetLineCap(ctx, uipath.lineCapStyle);
        CGContextSetMiterLimit(ctx, uipath.miterLimit);
        CGContextSetFlatness(ctx, uipath.flatness);
        [self strokeContext:ctx];
        CGContextRestoreGState(ctx);
    }
    [self strokeUIBezierPath:uipath];

    [uipath release];
    CGPathRelease(cgpath);
}

- (void)strokeContext:(CGContextRef)context
{
    CGContextStrokePath(context);
}

- (void)strokeUIBezierPath:(UIBezierPath*)path
{
    [path stroke];
}

снимок с приборов: Instruments snapshot showing equal performance