1

I'm trying to add a stroke to `CGPath` in a macOS app with the code below. The issue is that the stroked path can't be filled properly. With the current method, I'm adding the original path on top of the stroked path. This causes the overlapping parts to remain unfilled.

func getPreviewPath(path:CGPath, strokeWidth: CGFloat) ->CGPath{
    let window = NSWindow()
    let context = NSGraphicsContext(window: window).cgContext
            
    context.setLineCap(CGLineCap(rawValue: 0)!)
    context.setLineWidth(strokeWidth)
    context.setLineJoin(.bevel)
    context.addPath(path)
    context.replacePathWithStrokedPath()
    context.addPath(path)
    
    return context.path!
}

I've read that this is caused by the even-odd fill rule. But there doesn't seem to be a way to change this.

fatihyildizhan
  • 8,614
  • 7
  • 64
  • 88
thameem
  • 11
  • 1
  • 3
  • 4
    Your code doesn't do any actual drawing. There are no calls to `drawPath`, `fillPath`, or `strokePath`, or any other calls that actually modify pixels. You need to show us more code. – rob mayoff Jan 04 '23 at 19:59
  • For my use case I don't need to. I am passing a `CGPath` into this function and getting the stroked path as a `CGPath`. (see updated code) – thameem Jan 05 '23 at 08:54
  • Please add the code that actually draws the stroked path. – Codo Jan 05 '23 at 09:18
  • Drawing the path in swiftUI using `Path(getPreviewPath(path:input, strokeWidth: 20))` – thameem Jan 05 '23 at 09:29
  • Since this is obviously about SwiftUI, why don't you use the stroke method of SwiftUI's Path class? See https://developer.apple.com/documentation/swiftui/path/stroke(style:) Also note that your current code creates path the includes both the stroked and non-stroked path. – Codo Jan 05 '23 at 10:21

1 Answers1

0

Here's a minimal example using the stroke() method:

struct ContentView: View {
    var body: some View {
        Path { path in
            path.move(to: CGPoint(x: 20, y: 20))
            path.addLine(to: CGPoint(x: 50, y: 100))
            path.addLine(to: CGPoint(x: 80, y: 20))
            path.addLine(to: CGPoint(x: 120, y: 20))
        }
        .stroke(style: StrokeStyle(lineWidth: 12.0, lineCap: .round, lineJoin: .bevel))
        .padding()
    }
}

And this is the result:

Screenshot path stroking

Update

Her is a slightly more complex example. It first creates a path containing a stroked path. This path is then filled. The result is the same.

struct ContentView: View {
    var body: some View {
        Path(createPath())
            .fill(.black)
        .padding()
    }
    
    func createPath() -> CGPath {
        let path = CGMutablePath()
        path.move(to: CGPoint(x: 20, y: 20))
        path.addLine(to: CGPoint(x: 50, y: 100))
        path.addLine(to: CGPoint(x: 80, y: 20))
        path.addLine(to: CGPoint(x: 120, y: 20))
        return path.copy(strokingWithWidth: 12.0, lineCap: .round, lineJoin: .bevel, miterLimit: 10)
    }
}
Codo
  • 75,595
  • 17
  • 168
  • 206
  • Did try this earlier. It works well with paths with no fill(unclosed paths). The issue I ran into with this is that, when getting the stroked `CGPath` from `strokedPath.cgPath` closed paths are unfilled. When trying to fill back the paths I run into the same issues as described in the initial question. – thameem Jan 06 '23 at 07:10
  • I don't understand what you are actually trying to achieve and why you want to use `strokedPath` instead of directly stroking the path. You might want to add a description of the context and a minimal reproducible example. – Codo Jan 06 '23 at 07:50
  • `strokedPath` here is `Path` in your answer as a variable. Ultimately I need a stroked path as a `CGPath`. – thameem Jan 06 '23 at 10:15
  • I still don't understand why you need to do it this particular way. Obviously, you are not willing to share what you are actually trying to achieve. But making another shot guessing what might help you, I've added additional code that first creates a path containing a stroked path. – Codo Jan 06 '23 at 17:04