Applying rounded corners to paths/polygons
Asked Answered
B

3

19

I'm gathering some info for a project that has to start within a few weeks. This project contains a browser-based drawing tool where users can add predefined shapes or forming shapes themselves. Shapes must be selectable, freely scalable and rotatable with a Illustrator-like transformtool (handles). Predefined shapes that we have in mind are: rectangles, ellipses, half ellipses and (isosceles) triangles.

So far so good, to achieve this I was thinking of RaphaelJS or FabricJS but... Every shape (polygon/path) must be drawn with a certain cornerradius. And the cornerradius must be maintained while scaling, so no distortion occurs. The user can specify the rounding by input.

There's a few obstacles/questions:

  • Is there some uniform mathemetical formula to apply a cornerradius to the shapes I mentioned? Or must every shape be treated as a mini-project itself? I want to return it as a path or poly, so it can be drawn with SVG or canvas.
  • Every scale or rotate operation, by dragging the transformhandles, will result in (massive) calculations to retrieve an updated shape I think. Rectangles are the easiest to achieve and except ellipses, all the other shapes will be a lot harder to compute. Is there some way to speed up the process?

I found a site where users can draw flowcharts and apply a cornerradius on almost all shapes the are offered. It works so smoothly, I can't nail how they did it. Link: https://www.lucidchart.com/ (try button)

For now, I'm a bit clueless, I guess to mediocre in mathematics. Perhaps someone can push me in the right direction and share some experiences?

Thanks in advance.

BTW. Performance is key in this project. The ouput of the drawing must be SVG format.

Blink answered 9/10, 2013 at 10:34 Comment(0)
V
52

I ended up having a similar problem, and wasn't able to find a simple solution. I ended up writing a fairly generic corner-rounding function based on Adobe Illustrator's operation. It uses Bezier curves instead of arcs, but I think the result is pretty decent.

It supports rounding with a radius given in the coordinate space of the SVG image or as a fraction of the distance between a corner and its neighbors.

To use this, include rounding.js in your project and call the function:

roundPathCorners(pathString, radius, useFractionalRadius)

The code and some test paths are here: http://embed.plnkr.co/kGnGGyoOCKil02k04snu/preview

This is how the examples from the Plnkr render:

SVG Path Rounding Examples

Voluntary answered 20/11, 2014 at 4:39 Comment(4)
I upvoted this answer because the javascript attached has the funnest bugs/undocumented-features I've seen in a while.Donar
I'm guessing you're referring to the complete lack of bounds-checking for the radius parameter? I too was amused, and figured people could fix it if they wanted to :)Voluntary
Is it possible to create circular bezier curves? I know quadratic bezier curves are just parabolas.Gamesmanship
@Gamesmanship No, bezier curves can't match circular arcs, but they are almost indistinguishable: spencermortensen.com/articles/bezier-circleAngstrom
C
2

The starting point could be using-svg-curves-to-imitate-rounded-corners. The principle is to convert every corner with shorthand relative cubic (s). This example is very basic and works only with two possible corner cases.

I think expanding this like corner replace with shorthand relative cubic is possible to expand to cover also other path segments. Every segment has a on-curve coordinate point, which have to be replaced with s segment. The math can be interesting part of this solution.

Cheerly answered 23/10, 2013 at 12:22 Comment(0)
S
2

Despite this question being around for some time, some may stop by and try this solution:

var BORDER_RADIUS = 20;

function roundedPath( /* x1, y1, x2, y2, ..., xN, yN */ ){
    context.beginPath();
    if (!arguments.length) return;

    //compute the middle of the first line as start-stop-point:
    var deltaY = (arguments[3] - arguments[1]);
    var deltaX = (arguments[2] - arguments[0]);
    var xPerY = deltaY / deltaX;
    var startX = arguments[0] + deltaX / 2;
    var startY = arguments[1] + xPerY * deltaX / 2;

    //walk around using arcTo:
    context.moveTo(startX, startY);
    var x1, y1, x2, y2;
    x2 = arguments[2];
    y2 = arguments[3];
    for (var i = 4; i < arguments.length; i += 2) {
        x1 = x2;
        y1 = y2;
        x2 = arguments[i];
        y2 = arguments[i + 1];
        context.arcTo(x1, y1, x2, y2, BORDER_RADIUS);
    }

    //finally, close the path:
    context.arcTo(x2, y2, arguments[0], arguments[1], BORDER_RADIUS);
    context.arcTo(arguments[0], arguments[1], startX, startY, BORDER_RADIUS);
    context.closePath();
}

The trick is to start (and stop) at the middle of the first line, and then use the arcTo function which is described very nicely here.

Now you "just" have to find a way to express all of your shapes as polygons.

Salmanazar answered 21/1, 2015 at 15:29 Comment(1)
context denotes the CanvasRenderingContext2DSalmanazar

© 2022 - 2024 — McMap. All rights reserved.