Skip to content
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

add section: area under a bézier curve #238

Open
jamesgk opened this issue Dec 17, 2014 · 11 comments
Open

add section: area under a bézier curve #238

jamesgk opened this issue Dec 17, 2014 · 11 comments

Comments

@jamesgk
Copy link

jamesgk commented Dec 17, 2014

@behdad and I recently came up with a relatively simple way to calculate the area under a cubic (or by simple extension, quadratic) bézier curve. He thinks this would be a great place to describe it.

Basically the equation is this (latex source in alt text):

A = \frac{3}{20} \begin{pmatrix} &x_0(&&-2 y_1&-y_2&+3 y_3&) \ +&x_1(&2 y_0&&-y_2&-y_3&) \ +&x_2(&y_0&+y_1&&-2 y_3&) \ +&x_3(&-3 y_0&+y_1&+2 y_2&&)\end{pmatrix}

We derive this equation first by taking the definite integral

\int_{0}^{1} f_y(t) f_x'(t) dt

Note that this yields a signed area, which in Processing's coordinate system is positive if the area lies to the left of the curve and negative if the area is to the right (reverse of a typical Cartesian system).

We know:

f_y(t) = y_0 (1-t)^3 + 3 y_1 t (1-t)^2 + 3 y_2 t^2 (1-t) + y_3 t^3

and this equation's derivative:

f_x'(t) = -3 (x_0 (-1+t)^2 + x_1 (-1 + 4 t - 3 t^2) + x_2 (-2 t + 3 t^2) - x_3 t^2)

and so the integral comes out to:

\frac{1}{20} \begin{pmatrix} &x_0(&-10 y_0&-6 y_1&-3 y_2&-y_3&) \ +&x_1(&6 y_0&&-3 y_2&-3 y_3&) \ +&x_2(&3 y_0&+3 y_1&&-6 y_3&) \ +&x_3(&y_0&+3 y_1&+6 y_2&+10 y_3&)\end{pmatrix}

We then subtract excess area between x0 and x3:

\frac{1}{2} (y_3 + y_0) (x_3 - x_0)

or:

\frac{1}{20} (10 (x_3 y_3) - 10 (x_0 y_3) + 10 (x_3 y_0) - 10 (x_0 y_0))

Then, factoring out 3 from the resulting coefficients gives us our final equation for A. Here is a method of your BezierCurve class demonstrating this calculation:

  float getArea() {
    if (points.length < 3) return 0;
    float x0 = points[0].x, y0 = points[0].y,
      x1, y1, x2, y2, x3, y3;
    if (points.length == 4) {
      x1 = points[1].x; y1 = points[1].y;
      x2 = points[2].x; y2 = points[2].y;
      x3 = points[3].x; y3 = points[3].y;
    } else if (points.length == 3) {
      x1 = points[0].x * 1 / 3 + points[1].x * 2 / 3;
      y1 = points[0].y * 1 / 3 + points[1].y * 2 / 3;
      x2 = points[2].x * 1 / 3 + points[1].x * 2 / 3;
      y2 = points[2].y * 1 / 3 + points[1].y * 2 / 3;
      x3 = points[2].x; y3 = points[2].y;
    } else {
      return 0; // just return zero if order > 3
    }
    return (
        x0 * (      - 2*y1 -   y2 + 3*y3)
      + x1 * ( 2*y0        -   y2 -   y3)
      + x2 * (   y0 +   y1        - 2*y3)
      + x3 * (-3*y0 +   y1 + 2*y2       )
    ) * 3 / 20;
  }

Hopefully you think this is useful, and I didn't make any mistakes! Let us know what you think.

@behdad
Copy link

behdad commented Dec 17, 2014

Thanks James. Note that the final getArea() is not "area under the curve anymore". That would have been the initial integral, before subtracting the area under the line segment p0-p3. After the subtraction this because the signed area in the closed shaped formed by the curve and the line segment p3-p0. We can call this the area of the curve. Ie, if the curve is a degenerate line segment, the area is 0.

@Pomax
Copy link
Owner

Pomax commented Dec 17, 2014

yeah, "area under a curve" for parametric curves is kind of hard due to the absence of a strict "above" or "below", but getting the area between the curve and its start/end line segment is still pretty useful! Nice stuff, it's definitely something worth adding to the article!

You might be able to simplify the formulae by axis aligning the curve first, so that some of the terms fall away (x0, y0 already fall away after a translation, y3 would fall away after a rotation but then requires introducing potential imprecisions, so might not be worth it)

@jamesgk
Copy link
Author

jamesgk commented Dec 17, 2014

In fact, this only calculates the "area of a curve" as I would intuitively understand it so long as the curve has no inflection points. Area to the left of the curve has different sign as area to the right, so they cancel and e.g. an S-shaped curve will have an "area" of zero. But maybe "area of a curve" is still an accurate-enough name.

We discussed the idea of aligning the curve before calculating the integral, but I don't think it actually simplifies the formulae per se considering the additional calculations needed to transform the points in the curve. Though I suppose that's pretty subjective.

In any case, would you like us to create a pull request with this code? I'd be happy to but I thought you might want to write the actual section in the primer yourself? But I'd be happy to do that as well.

@behdad
Copy link

behdad commented Dec 17, 2014

Write. It's still the "signed area".

@Pomax
Copy link
Owner

Pomax commented Dec 17, 2014

as a lossless operation the translation by -(x,y)0 seems a step that generally makes sense (if only to homogenise with most other papers I've read that try to use canonical curve forms). But yes, if you want to do a PR with the code I'd be more than happy to merge it in!

I'm also working on a newer pure JS rather than Processing bezier library that I want to swithc over to probably over the holidays too, so if you'd rather file it against that over on https://github.com/Pomax/bezierjs then that'd be great, too. I'm happy to port if you don't, though!

If you do, though, the current active branch is the browserify one, because I started writing bezier.js as a single file and ran into the "what am I doing" wall, it's been split up with a browserify build step instead, but I've not merged that to master yet.

@jamesgk
Copy link
Author

jamesgk commented Mar 9, 2015

Even more interesting -- doing this calculation with a quadratic curve and translating by -(x0,y0) yields:

(x2 * y1 - x1 * y2) / 3

or, one third the cross product of (x1,y1) and (x2,y2), i.e. one third the area of the parallelogram with these vectors as sides, i.e. two thirds the area of the triangle made by the control points! Which is a nice relationship. Behdad and I are thinking that there may be a similarly nice way to relate the area of a cubic curve to the polygons made by its control points, but we haven't looked into it too deeply.

I'm going to upload the code for the quadratic area, if you care to pull it into bezierjs. I might also make a pull request for bezierinfo, though I was having a little trouble visualizing the signed area with the Processing library.

@Pomax
Copy link
Owner

Pomax commented Mar 9, 2015

bezierjs is pure javascript though, not Processing =)

@jamesgk
Copy link
Author

jamesgk commented Mar 9, 2015

Right, the trouble I was having was with bezierinfo. I can't remember what the problem was off the top of my head, but perhaps it was related to the issue you recently posted. I'll come back to it eventually.

@Pomax
Copy link
Owner

Pomax commented Mar 9, 2015

cool. I'm still trying to figure out a good way to migrate bezierinfo to bezierjs instead - with the recent developments in React, it feels like I can significantly improve things by exploiting React as "section elements" including per-section native code.

@deanm
Copy link

deanm commented Aug 9, 2016

I just stumbled across this discussion, and just in case anyone is interested I have some old notes on the topic:

http://deanm.github.io/graphics/2016/03/30/CurveArea.html

It perhaps provides some more references, the cubic equations were worked out long ago (in a metafont discussion from 2000), and there is a good writeup of it by Jackowski (https://tug.org/TUGboat/tb33-1/tb103jackowski.pdf).

The main point of the above article was trying to tackle the equation for rational quadratic Béziers, I haven't been able to find anywhere that has been done before. It's a bit tricky but should be usable.

@behdad
Copy link

behdad commented Aug 11, 2016

@deanm Thanks for the writeup. Saved me writing that in the future!

I ended up using sympy to implement Green's theorem and apply it to find mean, variance, and covariance as well: https://github.com/behdad/fonttools/blob/5cd0a55635062f382ff86ccea6e42bbf9b8b030a/Snippets/symfont.py

@Pomax Pomax transferred this issue from Pomax/bezierinfo Jan 24, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants