问题
Core Graphics has two functions, CGPathCreateCopyByDashingPath()
and CGPathCreateCopyByStrokingPath()
, that both take a CGPath that you want to stroke and convert it into its equivalent fill. I need to do this so I can, for example, stroke a line with a gradient: call CGPathCreateCopyByStrokingPath()
, load the path into the CGContext, call CGContextClip()
, and then draw the gradient.
However, CGPathCreateCopyByStrokingPath()
accepts line stroking parameters like line cap, line join, etc., while CGPathCreateCopyByDashingPath()
does not. I would like to be able to dash with a custom line cap/join.
In particular, both functions have the following in their documentations:
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.
Emphasis mine. So what I take out of this is that once you call either function, you get a new path consisting of the lines that bound the requested stroke. So if I call ByDashing
and then ByStroking
, the first will create a path consisting of a bunch of little rectangles, and then the second will make the rectangles that form the perimeter lines of those little rectangles, which is not what I want. (I can test this and post pictures later.)
Everything I've seen points to Core Graphics being able to do this directly with a CGContext; for instance, the Programming with Quartz book shows round and square line caps in its dashing example. Is there any reason I can't do that with a standalone CGPath?
Am I missing something? Or am I just stuck with this?
This is for OS X, not for iOS.
Thanks.
回答1:
I'm not sure what the problem is, really. You have a path to start with; let's call it A
. You call CGPathCreateCopyByDashingPath()
to make a new path from A
that has the dashing you want; let's call that B
. B
does not have particular line caps/joins set on it, because that is not a property of a path, but rather properties used when stroking a path. (Imagine hand-making a dashed path by drawing line segments from point to point; nowhere in that path description is there any concept of caps or joins, just start and end points for each segment.) Then take B
and call CGPathCreateCopyByStrokingPath()
on it to get C
, a fillable path for the stroke of B
using particular line width/cap/join characteristics. Finally, fill C
using your gradient fill. Does that not work? It seems like you know about all the components that you need to solve your problem, so I'm not sure where the problem actually lies; can you clarify?
回答2:
Turns out the documentation for CGPathCreateCopyByDashingPath()
is wrong.
Right now, it says
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.
This implies that it produces the resultant path with default stroke parameters. But it doesn't! Instead, you get a new path that is just the existing path broken up by the dashing parameters. You will need to call CGPathCreateCopyByStrokingPath()
to produce the path to fill instead.
The following program has three sections. First it shows what the path should look like by drawing with CGContext
functions instead of CGPath
functions. Second, it draws with only CGPathCreateCopyByDashingPath()
. Notice how stroking the path doesn't produce a bunch of boxes where the dash used to be, but a bunch of dashes. If you look closely, you'll see a very small blue ill where the line joins are. Finally, it calls CGPathCreateCopyByDashingPath()
followed by CGPathCreateCopyByStrokingPath()
, and you will see that filling that produces the correct output.
Thanks again, bhaller! I'm not sure what the documentation shoul be changed to, or how to request such a change.
// 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;
}
来源:https://stackoverflow.com/questions/33130377/how-can-i-combine-cgpathcreatecopybydashingpath-and-cgpathcreatecopybystroking