fonttools / skia-pathops

Python bindings for the Skia library's Path Ops
https://skia.org/docs/dev/present/pathops/
BSD 3-Clause "New" or "Revised" License
47 stars 14 forks source link

[question] Would Skia PathOps in principle able to do subtraction _without_ implying "union" on the subject? #74

Closed justvanrossum closed 1 month ago

justvanrossum commented 1 month ago

Before:

image

After subtracting the selected shape from the rest:

image

It would be nice if it were possible to keep the overlaps in the subject shape.

anthrotype commented 1 month ago

I see what you mean, but it's not us (python bindings) who are doing that, we are just calling the Skia PathOps C++ API (Op function or SkOpBuilder class) for binary operations like difference or intersection. I suspect the pre-semplification of each operands is needed to more easily perform the path operation by clearly distinguishing the two, I don't know.

justvanrossum commented 1 month ago

but it's not us (python bindings)

I know that, but to ask a question to the Skia library community is a way higher threshold... A long shot, for sure.

justvanrossum commented 1 month ago

Thank you for your response!

anthrotype commented 1 month ago

no worries.. Yeah I know. Skia is a bit of a black box for us too 😅

Are you perhaps using OpBuilder? I see that one is effectively calling Simplify for each subpath as well as at the end (upon resolve), see: https://github.com/google/skia/blob/main/src/pathops/SkOpBuilder.cpp

Maybe you could try using the alternative op function (wrapper for the C++ Op function), and see if that changes the result? I don't see Simplify being called repeatedly in here (but I have only skimmed this and haven't tried myself): https://github.com/google/skia/blob/main/src/pathops/SkPathOpsOp.cpp

justvanrossum commented 1 month ago

Interesting! I will try to study and understand that, thanks.

anthrotype commented 1 month ago

I mean this: https://github.com/fonttools/skia-pathops/blob/815070e964334ec6cdf453f2d859fe2db49d9b06/src/python/pathops/_pathops.pyx#L1552

like used in here for the binary operations.py: https://github.com/fonttools/skia-pathops/blob/815070e964334ec6cdf453f2d859fe2db49d9b06/src/python/pathops/operations.py#L51

justvanrossum commented 1 month ago

We're indeed using OpBuilder. Thanks for the pointer! Will report back.

anthrotype commented 1 month ago

in theory they should produce the same output, OpBuilder being more efficient when one wants to apply a chain of path operations, as opposed to just one Op between two sets of 'subject' and 'clip' contours.

anthrotype commented 1 month ago

It would also be interesting to see what booleanOperations do in this case. I tried to design skia-pathops interface to be a drop-in replacement for booleanOperations (in fact ufo2ft can select one or the other). The binary ops (difference, intersection, reverse_difference, xor) take two lists of contours respectively for subject and clip ('contours' are any objects with a draw(pen) method), whereas union only takes a single list. All operations also take a pen to output the result.

justvanrossum commented 1 month ago

I've tried with booleanOperations in DrawBot, and it also seems to remove overlaps.

justvanrossum commented 1 month ago

I've tried with the raw "op" method instead of OpBuilder: no difference.

anthrotype commented 1 month ago

hm well it was worth a try. I don't think booleanOperations attempts to simplify the contours before processing the binary operations, so if that occurs it must be happening in the underlying Clipper library. Maybe it is in fact necessary for the algorithm to work. If you want to avoid this you'd have to only pass the contours that you want to participate and exclude the ones you don't want to be modified, but I understand this is tricky for fontra from a UI/UX perspective.

justvanrossum commented 1 month ago

It's not about not-participating paths: it could be self-overlapping, or two paths that overlap each other, and after subtraction of another shape could still be overlapping.

For example, before:

image

After:

image

For a designer, the fact that the two subject paths merge is unexpected.

anthrotype commented 1 month ago

good point. Seems like a hard problem and I'm afraid I don't have a solution right now.

justvanrossum commented 1 month ago

Thanks for thinking along. I posted a feature request for the future Kurbo path operations feature :)

anthrotype commented 1 month ago

I posted a feature request for the future Kurbo path operations feature :)

good idea! This looks beyond my pay-grade but possibly within Raph's :)

I think at least for the case of multiple mutually overlapping subject contours (as in the screenshot) one may perform the difference operation using the same clip path for each distinct subject contour (as opposed to a group of subjects ) such that they will continue to stay distict instead of being merged with one another. However for a single self-overlapping subject contour, that would not work.

justvanrossum commented 1 month ago

I think at least for the case of multiple mutually overlapping subject contours (as in the screenshot) one may perform the difference operation using the same clip path for each distinct subject contour

I was thinking of that as well, but it becomes hard when there are countershapes involved. I even tried this, with setting "fix direction" to False, but somehow failed to get the correct cut for the counter. Maybe I didn't try hard enough.

image
adrientetar commented 1 month ago

I think at least for Skia, it doesn't care about keeping overlapping paths since it's a rendering library. The path simplification step that it does first probably simplifies the subsequent boolean op logic.