-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Parametrized stroke outlining / expanding / offsetting #371
Comments
The Roadmap says it is planned: "Parametrizable path offsetting / stroking, to expand strokes to outlines and optionally produce all kinds of expressive strokes easily." @lehni probably has already some ideas for it and I am also interested in that topic. But more from a calligraphic point of view. When smoothing will work sufficiently outlining would be the next thing I would concentrate on. Until then you are a bit on your own I guess. But I think contributions are always welcome anyways! |
A friend pointed me to a pdf that could maybe be a useful starting point in this topic: |
Very interesting approach. I have always thought of the two edges of font characters to be somewhat independent of each other, though I suppose there's always a centerline that could be reverse-engineered. It sounds like the question of how this is done programmatically is still up for some debate. In the world of offsets there are lots of problems in the forms of singularities, loops, corners, etc, all of which are compounded by the lack of a clear and consistent mathematical formula connecting a given curve and its offset, except in rare degenerate cases. I'll see if I can work up something useful when I get a moment. I'm a little underwater with lots of projects right now, but I'll get back to it soon. Thanks for the reference, and the replies! |
@lehni @pseaton But he also did not solve some problems that appear with his implementation like shown in chapter 10. |
@pseaton I just found two other example made by Pomax where he put his math into action: Here you can click to generate a random curve and see his outline algorithm. It works quite ok but fails in some edge cases: And he made a nice little stroke-based CJK character builder that also works quite nice if you don't trick it too much: It also supports changing the thickness and different stroke endings: |
Thanks a lot @christophknoth UPDATE: Ported from SWF to Canvas 😉 and added variable offsetting Here is a dirty demo in flash (is an old demo): http://microbians.com/?page=code&id=code-bezieroffsetingplayground And a drawing application on I use the paper bu with variable offseting. I will release the code soon ;) |
@hkrish is working on a precise implementation of this that works better than the approach described by Pomax. I'm not sure how far away it still is. |
This has been in the works for some time. One of our main references was the paper http://visualcomputing.yonsei.ac.kr/papers/1997/compare.pdf, comparing different methods for offsetting planar bezier curves. And we chose the adaptive least squares method by J. Hoschek. I had made this demo/test page some time ago. The method is actually quite simple, general (least-sqrs and subdivision) and naturally extends to variable offsetting etc. You can check it out at, As long as we are working on just one cubic bezier segment most of these methods perform well, and definitely Hoschek's is one of the most performant and precise. However everything is downhill from there onwards —I am talking about linking up individual curves while making sure we are not breaking continuity between adjacent curves and discarding invalid parts when we offset a piecewise bezier curve; and in paperjs, we need to have a method that is robust and fast enough to handle most of, if not all, the corner cases. Hoschek's method has another advantage that it allows us to keep the tangent continuity between two adjacent paths. The methods outlined by Pomax (seems to me that it is an extension of Cobb's method "Design of Sculptured Surfaces Using The B-spline Representation", I may very well be wrong here!), although simple, may not suit our purpose well (see the comparison paper for details). I am hoping to finish this up in a week or two. P.S. @lehni I think I can send some code your way before Easter. :) |
@hkrish Exciting! |
+1 for this functionality |
Yes we're working on it. It'll take more time. |
I have been working separately on this issue using a different approach than Hari. Instead of creating the outer offset and inner offset path, I cut the path into curves. Then I subdivide each curve into several parts and apply an offset algorithm to each subcurve. Finally the offsets of all subcurves must be combined. The code for the subdivision of a cubic curve and the offset algorithm for the subcurves is more or less finished. After cleaning it up a bit I will post it here, so we have a second starting point for implementing the path offset feature. So far it looks like the implementation works for an arbitrary cubic bezier curve and IMHO the result is quite satisfying. The whole algorithm is rather simple, which on the other hand makes it quite fast. Here are two examples: But there is one remaining obstacle: @lehni or @hkrish, do you see any chance that the boolean operations can be improved, so they can handle overlapping paths, self intersecting paths, and in general produce more predictable results (the usage of a random number seems to produce, well, random results :-))? I think if the issues with the boolean operations can be solved, there is nothing that can stop Paper.js from getting a very nice path offset feature. Thanks, Jan |
Your code seem to work pretty well for single curves; how does it work for paths? Anyway, I need to focus a bit on the boolean code. We have the basics in place to remove the dependency on random (a new cubic solver etc.). I will hopefully get some time these coming weeks, and keep you guys updated. /hari On 2014Oct16, at 07:50 pm, Jan [email protected] wrote:
|
@hkrish The code only produces outlines for single curves. But since a path is simply a chain of curves, the offsets for the path can be created with applying the boolean unite on the curves' offsets. The only problem are the corners at the path segments. When iterating through the curves of a path, handling the corners could be done like this:
Here is a (not very good) illustration on where the triangle needs to be: Actually, I think the really difficult part is to resolve the issues with the boolean operations. I would love to help, but I fear that this goes beyond my capabilities. One thing that I can do is trying to implement the corner handling described above to see if it works. Jan |
Maybe @frank-trampe can help with the boolean; he worked on FontForge's this year for me, and there's also http://www.angusj.com/delphi/clipper.php which is used in RoboFont |
@iconexperience, very interesting! I look forward to seeing it all in action. Your plan how to merge the curves sounds good. We'll have to see how the two approaches perform and how precise their results will be. Boolean operations will also be required to handle strokeCap, for either solution. And the mentioned issues with the boolean code are well known, it's just a question of available time at the moment to fix them. @davelab6, @frank-trampe, help is always welcome! But as far as I understand, Clipper only handles polygons, not bezier curves, so it won't be of much use here. |
On 17 October 2014 00:28, Jürg Lehni [email protected] wrote:
Its used in RoboFont on Bezier paths, via |
@davelab6 yeah but it looks like the paths are flattened beforehand, which is far from ideal, no? |
I have written code for calculating an offset curve from a bezier curve in haskell: For boolean operations I am working on a line sweep algorithm, which will work in (n+m)log(n+m) for n points and m intersections. It's an adaptation of Bentley Ottman (http://en.wikipedia.org/wiki/Bentley%E2%80%93Ottmann_algorithm), and uses the bezier clipping algorithm for finding intersections (http://tom.cs.byu.edu/~557/text/cagd.pdf). |
I forgot to mention that Chebychev polynomials also are a good candidate for interpolation. They have the good property of keeping the maximum error minimal (rather than the mean error when using least squares). It may be interesting to compare these different approximations for speed and accuracy. For real-time use, a faster algorithm that generates many bezier curves may be better, while for generating an output file, a slower algorithm that has better accuracy may be desirable. |
A rough outline of my boolean operations algorithm can be found here: http://kuribas.github.io/omegafont/algorithms/overlap.html |
Hello @kristof, for offset curves, we chose the method due to Hoschek et.al. All those edge cases! Our presently-unfinished implementation in paper.js is complete and quite Again, @kristof, the sweep-line boolean ops sounds awesome. I am really Although as a first priority I am committed to solving the problems in I have a question related to this, any information regarding this is Thank you. /hari On Fri, Oct 17, 2014 at 3:38 AM, Kristof Bastiaensen <
|
hi @hkrish. If I am not mistaken, the Hosheks method is similar to mine, except that I try to improve the guesses of the sample points for calculating the least squares solution. This is slower, but gives a more accurate curve. This matters for me, because I want to calculate the least amount of subcurves, and speed is secondary for my application. I think snap rounding may work for bezier curves. In my algorithm I used snapping to other points, but the algorithm gets very complicated, so it may be worthwhile to look at snap rounding instead. The important thing when rounding is to look out for changes in topology, because they may cause errors in the output. If I can help with your current issues, let me know. |
@hkrish, @kuribas do you think both methods could perhaps be combined, with a optional argument that decides weather the algorithm should be optimizing either for speed or quality of the resulting curves? When experimenting with the code that @hkrish has already written, one of the remaining concerns I had was the production of too many sub-curves under certain circumstances. If the two approaches could be combined, that would be ideal! @hkrish, is it too early to share code? Would a collaboration make sense here? (Not intending to step on anyone's toes here, just really excited about the activity in this thread : ) |
I have created a Plunker for my code on offsetting a single cubic bezier curve. You can drag the end points and handles of the curve, zoom in and out (+ and - key), pan (arrow keys), and change the offset distance. I found playing around with it to be quite addictive. You can find the plunker here: http://embed.plnkr.co/jkNGoe8fD0li9DmY7u76/ (note that there seem to be problems with Safari and Internet Explorer) My aim was to have a starting point for creating curve offsets that are visually "good enough" with breaking the curve into as few sections as possible. My aim was not to create mathematically precise offset curves, so please do not use any of this code for technical applications. The result is far from perfect, parts of the code are rather clumsy and the mathematics are not very sophisticates, but it allows changing parts of the algorithm quite easily, so if you like to tinker around a little bit you may find this useful as a simple base application. Some details about my algorithm:
What I cannot do at the moment is to take the merged offset path and remove all the self interesections. If someone can point me to a document that shows how this can be done, I would be very thankful. That's all for now, I hope you enjoy playing around with it. Jan |
@kuribas wrote above on this topic,
and he also did http://kuribas.github.io/omegafont/algorithms/overlap.html :) |
It seems this feature is still missing officially. I was design an animation editing tool and ran into this problem. I found that by the offset algorithm, there are some unwanted self-intersections. I was intend to implement same funtionalities as Adobe Illustrator's offset path and expand stroke. |
@luz-alphacode this looks great! After looking at your code, I am wondering though: Did you consider using the work I posted above as a starting point? You can see it at http://bl.ocks.org/lehni/raw/9aa7d593235f04a3915ac4cef92def02/ The actual code you can see here: http://bl.ocks.org/lehni/raw/9aa7d593235f04a3915ac4cef92def02/offset.js The offsetting code itself works really well and is based on A New Shape Control and Classification for Cubic Bezier Curves by Shi-Nine Yang and Ming-Liang Huang. The only reason why I haven't added this to paper.js yet is issues with self intersection when expanding the offset to outlines. These issues are in the boolean operations code, and I so far have not managed to find more time to try and resolve them. Could you explain how you addressed the issues with self-intersection in your code? Can we merge these two efforts into something that can be integrated in the library officially? |
I've test my code again and found that my solution of finding self intersections performs reasonable for closed path but will fail on some type of open paths, and I've got some ideas about improving it. I'll check these ideas and come back later for dicussion. @lehni ... |
Great to see progress on this feature! If it's of any help, I've run a few tests using the latest code from @lehni and found a bit of weirdness in certain cases where a point's handles are aligned perfectly (example below)
|
Any updates / release times please? |
@shagarah I've worked some more on it recently, but am again swamped with work currently, so can't give a timeline. I want to release this soon as part of a final v1.0.0, but it needs more tweaking to be reliable enough. |
Hey guys, is there any progress on this? |
Wow I did not realize how complicated that is. Started some project with paper.js assuming this would be a nobrainer and now I‘m stuck. What is the current status here? Are there any workarounds via external libraries? Thanks |
@northamerican I should have time to start working on this again in about 6 weeks. Please get in touch at [email protected] so we can discuss this further. Thanks! |
@northamerican did you try to get in touch? I didn't receive anything :) |
@lehni e-mail has been sent. ready to discuss when you are. |
@lehni: Thanks very much for your work on this. I've got a question/comment about your offset code posted above: If I understand correctly the connect method works by connecting offset segments using the join-type set through the paths stroke style (round, bevel). Is there an exact definition on how the outline feature should work in this regard ? Is there a way to connect segments by "elongating" them and computing their intersections - this works for straight lines - I'm not sure about bezier curves? About your statement above: "These issues are in the boolean operations code" - can you give some more hints on what is going wrong? I haven't found a reference to the boolean op code in your snippet. Thanks! |
I have written code for paper.js for offsetting, please find it here : https://github.com/NoZ4/paper.js-offsets I found offsetting curves and paths are easy, its the self intersections that are the real problem along with floating point errors Also even if curves dissapper becouse of offsetting the points where curves originally touched must be taken into account I have code creating paths along these points, with arcs. it works with compound paths, using winding rules, so doughnut shapes can be shrunk/expandid. I know my code has better accuracy than inkscape, i haven't tested against others. Im quite cheery that if you expand enough the paths become a circle, or if shrunk turns into a point it takes into account where paths totally disapper becouse they cant be offset that much and curves/paths inverseing I got examples on github that show what i mean. I really hope this can be of use otherwise ive wasted alot of time on nothing |
Hi, first I wanted to say that I recently started using paper.js and I am amazed. I am porting a tool that renders shapes from Java to Javascript and paper.js is making this very easy. There is one problem that I am am not able to figure out. I need to be able to render complex paths and find the bounds of these paths. It looks like I can use offset (if it's ready?) but is there a way to render double strokes or other styles? (Similar to below?). http://www.jhlabs.com/java/java2d/strokes/ My most basic use case is to draw and capture the bounding rect of a double/triple dashed line (Similar to what PowerPoint calls a Compound Line) https://www.officetohelp.com/powerpoint/how-to-make-compound-line-in-ppt.html In Java2D these can be done by create a 'Brush' that walks path segments and allows for the ability to render something at each segment. (The work involved is very complicated because of rotations, miter sizes, drawing joins and all the other complicated things that people here understand better than me. |
hiya, dashed line should be easy just create gaps in the inital path. brushing is something we dont have at the moment i think, but shouldnt be too hard to impliment, get even points along a path and just clone whatever you want onto that posistion. Im currently working on my code, Im not doing double/triple line but am doing lines with variable thickness etc i'll be updating in about a week if thats any use |
NoZ4. Thank you for the response. I will wait for your updates and then try to build a double/triple stroke using your technique. During my research I didn't find any JavaScript implementations of brushing. (I assume because of off the hidden complexity, google slides has an internal one they use) but paper.js seems to be very close. I thought I would also add a picture from Powerpoint to show you the effect that I am trying to achieve. Looking forward to trying this out! |
Hiya everyone Just thought i would give a update, my code now allows to stroke a path theres a few known issues, shown on the page @lehni do you think it would ever be a idea to add this to the main code? also I used a bit of your code for offsetting single curves I think this might be a bit quicker than lehni's approach, since i got some escape early functions, but for things like square caps etc I think lehni's approach is much better. Thanks for all the thumbs up etc before :) Thanks everyone Noz |
hey, seekers of path offsets. with this external dependency, it isn't viable for inclusion in Paper of course but it's performant and reliable enough for my artistic uses. maybe it will come in use until we have native functionality working and approved. if you are curious as to how it applies smoothing to the path after offsetting, refer to paper-clipper/paperClipperSimplify.ts. it uses Paper's |
I've been following the library for some time, and have started a spare-time project based on it. I've been eagerly interested in the functionality to expand strokes into paths, particularly for bezier curves (where I know the problem is mathematically complex). Has a strategy / timeline for implementation been discussed? I may be able to contribute if new contributors are welcome.
I wasn't sure if the "outline" feature was separate from what I would describe in a CAD world as "offset". Is there some way to get equations for the position of the stroke edge that is not as complex as a full bezier-offset solution? See http://pomax.github.io/bezierinfo/#offsetting for a very detailed explanation of the math involved.
Thanks!
The text was updated successfully, but these errors were encountered: