我如何结合 CGPathCreateCopyByDashingPath() 和 CGPathCreateCopyByStrokingPath() 在 OS X 上绘制虚线 CGPath?

How can I combine CGPathCreateCopyByDashingPath() and CGPathCreateCopyByStrokingPath() to stroke a dashed CGPath on OS X?

Core Graphics 有两个函数,CGPathCreateCopyByDashingPath()CGPathCreateCopyByStrokingPath(),它们都采用您想要描边的 CGPath 并将其转换为等效的填充。我需要这样做,以便我可以,例如,用渐变画一条线:调用 CGPathCreateCopyByStrokingPath(),将路径加载到 CGContext,调用 CGContextClip(),然后绘制渐变。

但是,CGPathCreateCopyByStrokingPath() 接受线描边参数,如线帽、线连接等,而 CGPathCreateCopyByDashingPath() 不接受。我希望能够使用自定义行 cap/join.

特别是,这两个函数在其文档中都有以下内容:

The new path is created so that filling the new path draws the same pixels as stroking the original path with the specified dash parameters.

The new path is created so that filling the new path draws the same pixels as stroking the original path.

强调我的。因此,我从中得出的结论是,一旦您调用任一函数,您就会获得一条新路径,该路径由绑定所请求笔画的线条组成。因此,如果我调用 ByDashing,然后调用 ByStroking,第一个将创建一个由一堆小矩形组成的路径,然后第二个将创建形成这些小矩形周界线的矩形,这不是我想要的。 (我可以稍后测试这个和 post 图片。)

我所看到的一切都表明 Core Graphics 能够直接使用 CGContext 执行此操作;例如,《使用 Quartz 编程》一书在其虚线示例中显示了圆形和方形线帽。有什么理由我不能用独立的 CGPath 做到这一点吗?

我错过了什么吗?还是我只是受困于此?

这是为了 OS X,而不是为了 iOS。

谢谢。

我不确定问题出在哪里,真的。你有一条开始的路;我们称它为 A。您调用 CGPathCreateCopyByDashingPath() 以从 A 开辟一条具有您想要的潇洒的新路径;我们称之为 BB 没有在其上设置特定的行 caps/joins,因为那不是路径的 属性,而是在描边路径时使用的属性。 (想象一下通过从点到点绘制线段来手工制作一条虚线路径;在该路径描述中没有任何地方有任何上限或连接的概念,只是每个线段的起点和终点。)然后取 B 并调用CGPathCreateCopyByStrokingPath() 在其上获得 C,使用特定行 width/cap/join 特征的 B 笔划的可填充路径。最后,使用渐变填充填充 C。那行不通吗?似乎您知道解决问题所需的所有组件,所以我不确定问题到底出在哪里;你能澄清一下吗?

原来 CGPathCreateCopyByDashingPath() 的文档是错误的。

现在,它说

The new path is created so that filling the new path draws the same pixels as stroking the original path with the specified dash parameters.

这意味着它会生成具有默认笔划参数的结果路径。但事实并非如此!相反,您会得到一条新路径,它只是由破折号参数分解的现有路径。您将需要调用 CGPathCreateCopyByStrokingPath() 来生成要填充的路径。

下面的程序分为三个部分。首先,它通过使用 CGContext 函数而不是 CGPath 函数绘制来显示路径应该是什么样子。其次,它仅使用 CGPathCreateCopyByDashingPath() 绘制。请注意,抚摸路径不会在破折号所在的位置产生一堆框,而是产生一堆破折号。如果仔细观察,您会看到线条连接处有一个非常小的蓝色病灶。最后,它调用 CGPathCreateCopyByDashingPath(),然后调用 CGPathCreateCopyByStrokingPath(),您将看到填充 that 会产生正确的输出。

再次感谢,bhaller!我不确定文档应该更改成什么,或者如何请求这样的更改。

// 15 october 2015
#import <Cocoa/Cocoa.h>

@interface dashStrokeView : NSView
@end

void putstr(CGContextRef c, const char *str, double x, double y)
{
    NSFont *sysfont;
    CFStringRef string;
    CTFontRef font;
    CFStringRef keys[1];
    CFTypeRef values[1];
    CFDictionaryRef attrs;
    CFAttributedStringRef attrstr;
    CTLineRef line;

    sysfont = [NSFont systemFontOfSize:[NSFont systemFontSizeForControlSize:NSRegularControlSize]];
    font = (CTFontRef) sysfont;     // toll-free bridge

    string = CFStringCreateWithCString(kCFAllocatorDefault,
        str, kCFStringEncodingUTF8);
    keys[0] = kCTFontAttributeName;
    values[0] = font;
    attrs = CFDictionaryCreate(kCFAllocatorDefault,
        keys, values,
        1,
        &kCFTypeDictionaryKeyCallBacks,
        &kCFTypeDictionaryValueCallBacks);
    attrstr = CFAttributedStringCreate(kCFAllocatorDefault, string, attrs);

    line = CTLineCreateWithAttributedString(attrstr);
    CGContextSetTextPosition(c, x, y);
    CTLineDraw(line, c);

    CFRelease(line);
    CFRelease(attrstr);
    CFRelease(attrs);
    CFRelease(string);
}

@implementation dashStrokeView

- (void)drawRect:(NSRect)r
{
    CGContextRef c;
    CGFloat lengths[2] = { 10, 13 };
    CGMutablePathRef buildpath;
    CGPathRef copy, copy2;

    c = (CGContextRef) [[NSGraphicsContext currentContext] graphicsPort];

    CGContextSaveGState(c);

    putstr(c, "Dash + Stroke With CGContext Functions", 10, 10);
    CGContextMoveToPoint(c, 50, 50);
    CGContextAddLineToPoint(c, 100, 30);
    CGContextAddLineToPoint(c, 150, 70);
    CGContextAddLineToPoint(c, 200, 50);
    CGContextSetLineWidth(c, 10);
    CGContextSetLineJoin(c, kCGLineJoinBevel);
    CGContextSetLineCap(c, kCGLineCapRound);
    CGContextSetLineDash(c, 0, lengths, 2);
    CGContextSetRGBStrokeColor(c, 0, 0, 0, 1);
    CGContextStrokePath(c);
    // and reset
    CGContextSetLineWidth(c, 1);
    CGContextSetLineJoin(c, kCGLineJoinMiter);
    CGContextSetLineCap(c, kCGLineCapButt);
    CGContextSetLineDash(c, 0, NULL, 0);

    CGContextTranslateCTM(c, 0, 100);
    putstr(c, "Dash With CGPath Functions", 10, 10);
    buildpath = CGPathCreateMutable();
    CGPathMoveToPoint(buildpath, NULL, 50, 50);
    CGPathAddLineToPoint(buildpath, NULL, 100, 30);
    CGPathAddLineToPoint(buildpath, NULL, 150, 70);
    CGPathAddLineToPoint(buildpath, NULL, 200, 50);
    copy = CGPathCreateCopyByDashingPath(buildpath, NULL, 0, lengths, 2);
    CGContextAddPath(c, copy);
    CGContextStrokePath(c);
    CGContextAddPath(c, copy);
    CGContextSetRGBFillColor(c, 0, 0.25, 0.5, 1);
    CGContextFillPath(c);
    CGPathRelease(copy);
    CGPathRelease((CGPathRef) buildpath);

    CGContextTranslateCTM(c, 0, 100);
    putstr(c, "Dash + Stroke With CGPath Functions", 10, 10);
    buildpath = CGPathCreateMutable();
    CGPathMoveToPoint(buildpath, NULL, 50, 50);
    CGPathAddLineToPoint(buildpath, NULL, 100, 30);
    CGPathAddLineToPoint(buildpath, NULL, 150, 70);
    CGPathAddLineToPoint(buildpath, NULL, 200, 50);
    copy = CGPathCreateCopyByDashingPath(buildpath, NULL, 0, lengths, 2);
    copy2 = CGPathCreateCopyByStrokingPath(copy, NULL, 10, kCGLineCapRound, kCGLineJoinBevel, 10);
    CGContextAddPath(c, copy2);
    CGContextSetRGBFillColor(c, 0, 0.25, 0.5, 1);
    CGContextFillPath(c);
    CGContextAddPath(c, copy2);
    CGContextStrokePath(c);
    CGPathRelease(copy2);
    CGPathRelease(copy);
    CGPathRelease((CGPathRef) buildpath);

    CGContextRestoreGState(c);
}

- (BOOL)isFlipped
{
    return YES;
}

@end

@interface appDelegate : NSObject<NSApplicationDelegate>
@end

@implementation appDelegate

- (void)applicationDidFinishLaunching:(NSNotification *)note
{
    NSWindow *mainwin;
    NSView *contentView;
    dashStrokeView *view;
    NSDictionary *views;
    NSArray *constraints;

    mainwin = [[NSWindow alloc] initWithContentRect: NSMakeRect(0, 0, 320, 360)
        styleMask:(NSTitledWindowMask | NSClosableWindowMask | NSMiniaturizableWindowMask | NSResizableWindowMask)
        backing:NSBackingStoreBuffered
        defer:YES];
    [mainwin setTitle:@"Dash/Stroke Example"];
    contentView = [mainwin contentView];

    view = [[dashStrokeView alloc] initWithFrame:NSZeroRect];
    [view setTranslatesAutoresizingMaskIntoConstraints:NO];
    [contentView addSubview:view];

    views = NSDictionaryOfVariableBindings(view);
    constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-[view]-|"
        options:0
        metrics:nil
        views:views];
    [contentView addConstraints:constraints];
    constraints = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-[view]-|"
        options:0
        metrics:nil
        views:views];
    [contentView addConstraints:constraints];

    [mainwin cascadeTopLeftFromPoint:NSMakePoint(20, 20)];
    [mainwin makeKeyAndOrderFront:nil];
}

- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)app
{
    return YES;
}

@end

int main(void)
{
    NSApplication *app;

    app = [NSApplication sharedApplication];
    [app setActivationPolicy:NSApplicationActivationPolicyRegular];
    [app setDelegate:[appDelegate new]];
    [app run];
    return 0;
}