~koehr/catmullrom2bezier

b553dcb92dde5122d70752cd20931af869f59d8c — koehr 3 years ago cdf1ceb
ES6 and License adaption
3 files changed, 117 insertions(+), 155 deletions(-)

M LICENSE
M README.md
M catmullrom2bezier.js
M LICENSE => LICENSE +1 -18
@@ 1,4 1,4 @@
catmullrom2bezier - Catmull-Rom Spline to Bezier Spline Converter
catmullrom2bezier - Catmull-Rom Spline to Bezier Spline Converter ES6 version

The MIT License (MIT)



@@ 21,20 21,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

GNU GENERAL PUBLIC LICENSE (GPL)

Copyright (C) 2010 Douglas Alan Schepers

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

M README.md => README.md +22 -9
@@ 1,7 1,7 @@
catmullrom2bezier
=================

Catmull-Rom Spline to Bezier Spline Converter, from [Douglas Alan Schepers](http://schepers.cc/getting-to-the-point). Shepazu, if you want ownership of this repo, I'd be glad to transfer it to you.
Catmull-Rom Spline to Bezier Spline Converter, from [Douglas Alan Schepers](http://schepers.cc/getting-to-the-point).

This is an experimental extension of the SVG 'path' element syntax to 
allow Catmull-Rom splines, which differs from Bezier curves in that all


@@ 11,24 11,37 @@ This is intended to serve as a proof-of-concept toward inclusion of a
Catmull-Rom path command into the SVG 2 specification.  As such, it is 
not production-ready, nor are the syntax or resulting rendering stable;
notably, it does not include a 'tension' parameter to allow the author 
to specify how tightly the path interpolates between points.  Feedback
to specify how tightly the path interpolates between points. Feedback
on this and other aspects is welcome.
 
The syntax is as follows:

```
([number],[number])+  R([number],[number])+ ([number],[number])*
`([number],[number])+  R([number],[number])+ ([number],[number])*`

For example:

```html
  <path stroke="#BADA55" 
        stroke-width="2"
        fill="none" 
        d="M20,380 
           R58,342 
           100,342 
           100,300 
           140,250 
           190,210 
           220,197 
           250,184 
           280,155 
           310,260 
           404,20"/>  
```

In other words, there must be at least one coordinate pair preceding the 
Catmull-Rom path segment (just as with any other path segment), followed
by the new path command 'R', followed by at least two coordinate pairs,
with as many optional subsequent coordinate pairs as desired.  
with as many optional subsequent coordinate pairs as desired.
 
(As with path syntax in general, the numbers may be positive or negative 
floating-point values, and the delimiter is any combination of spaces 
with at most one comma.)

#License:
This code is available under the MIT or GPL licenses, and it takes 
inspiration from Maxim Shemanarev's Anti-Grain Geometry library.

M catmullrom2bezier.js => catmullrom2bezier.js +94 -128
@@ 1,147 1,113 @@
//************************************************
// 
// Catmull-Rom Spline to Bezier Spline Converter
//
// 
// This is an experimental extension of the SVG 'path' element syntax to 
// allow Catmull-Rom splines, which differs from Bézier curves in that all
// defined points on a Catmull-Rom spline are on the path itself.
// 
// This is intended to serve as a proof-of-concept toward inclusion of a 
// Catmull-Rom path command into the SVG 2 specification.  As such, it is 
// not production-ready, nor are the syntax or resulting rendering stable;
// notably, it does not include a 'tension' parameter to allow the author 
// to specify how tightly the path interpolates between points.  Feedback
// on this and other aspects is welcome.
// 
// The syntax is as follows:
// ([number],[number])+  R([number],[number])+ ([number],[number])*
// In other words, there must be at least one coordinate pair preceding the 
// Catmull-Rom path segment (just as with any other path segment), followed
// by the new path command 'R', followed by at least two coordinate pairs,
// with as many optional subsequent coordinate pairs as desired.  
// 
// (As with path syntax in general, the numbers may be positive or negative 
// floating-point values, and the delimiter is any combination of spaces 
// with at most one comma.)
// 
// License:
// This code is available under the MIT or GPL licenses, and it takes 
// inspiration from Maxim Shemanarev's Anti-Grain Geometry library.
// 
// Contact info:
// www-svg@w3.org for public comments (preferred),
// schepers@w3.org for personal comments.
// 
// author: schepers, created: 07-09-2010
// 
//************************************************


function init() {
  // find each path, to see if it has Catmull-Rom splines in it
  var pathEls = document.documentElement.getElementsByTagName("path");
  for (var p = 0, pLen = pathEls.length; pLen > p; p++) {
    var eachPath = pathEls[ p ];
    parsePath( eachPath );
/* Catmull-Rom Spline to Bezier Spline Converter
 *
 * Forked from github.com/ariutta/catmullrom2bezier
 * further: http://schepers.cc/getting-to-the-point
 *
 * License: MIT, see LICENSE file
 *
 * Contact info:
 * if it is about the algorithm or original source:
 *   www-svg@w3.org for public comments (preferred),
 *   schepers@w3.org for personal comments.
 * if it is about this library please use Github issues.
 *
 * © schepers 2010
 * ES6 library adaption @ koehr 2018
 */

// expects String describing path (d property of <path>)
// returns String describing Bezier equivalent
// NOTE: This code supports only absolute command coordinates.
function parsePath (d) {
  let pathArray = []
  let lastX = ''
  let lastY = ''

  // no need to redraw the path if no Catmull-Rom segments are found
  if (d.search(/[rR]/) < 0) {
    return d
  }
}

function parsePath( path ) {
  var pathArray = [];
  var lastX = "";
  var lastY = "";

  var d = path.getAttribute( "d" );
  if ( -1 != d.search(/[rR]/) ) {
    // no need to redraw the path if no Catmull-Rom segments are found
    
    // split path into constituent segments
    var pathSplit = d.split(/([A-Za-z])/);
    for (var i = 0, iLen = pathSplit.length; iLen > i; i++) {
      var segment = pathSplit[i];
    
      // make command code lower case, for easier matching
      // NOTE: this code assumes absolution coordinates, and doesn't account for relative command coordinates
      var command = segment.toLowerCase()
      if ( -1 != segment.search(/[A-Za-z]/) ) {
        var val = "";
        if ( "z" != command ) {
          i++;
          val = pathSplit[ i ].replace(/\s+$/, '');
        }

        if ( "r" == command ) {
          // "R" and "r" are the a Catmull-Rom spline segment

          var points = lastX + "," + lastY + " " + val;

          // convert Catmull-Rom spline to Bézier curves
          var beziers = catmullRom2bezier( points );
          //insert replacement curves back into array of path segments
          pathArray.push( beziers );
        } else {
          // rejoin the command code and the numerical values, place in array of path segments
          pathArray.push( segment + val );

          // find last x,y points, for feeding into Catmull-Rom conversion algorithm
          if ( "h" == command ) {
            lastX = val;
          } else if ( "v" == command ) {
            lastY = val;
          } else if ( "z" != command ) {
            var c = val.split(/[,\s]/);
            lastY = c.pop();
            lastX = c.pop();
          }
        }
  const pathSplit = d.split(/([A-Za-z])/)

  for (let i = 0, iLen = pathSplit.length; iLen > i; i++) {
    const segment = pathSplit[i]
    const command = segment.toLowerCase() // lower case for easier matching

    // whoops, not a command
    if (command.search(/[a-z]/) < 0) continue

    let val = ''
    if (command !== 'z') {
      i++
      val = pathSplit[i].replace(/\s+$/, '')
    }

    // "R" and "r" are the a Catmull-Rom spline segment
    if (command === 'r') {
      const points = `${lastX},${lastY} ${val}`
      const beziers = catmullRom2bezier(points) // convert to Bézier
      pathArray.push(beziers) // and put it back
    } else {
      pathArray.push(segment + val) // not our business, put back directly

      // find last x,y points, for feeding into Catmull-Rom conversion algorithm
      if (command === 'h') {
        lastX = val
      } else if (command === 'v') {
        lastY = val
      } else if (command !== 'z') {
        const c = val.split(/[,\s]/)
        lastY = c.pop()
        lastX = c.pop()
      }
    }
    // recombine path segments and set new path description in DOM
    path.setAttribute( "d", pathArray.join(" ") );
  }

  // return recombined path segments
  return pathArray.join(' ')
}

function catmullRom2bezier (points) {
  const crp = points.split(/[,\s]/).map(n => parseFloat(n))
  let d = ''

  for (let i = 0, iLen = crp.length; iLen - 2 > i; i += 2) {
    let p = []

function catmullRom2bezier( points ) {
  // alert(points)
  var crp = points.split(/[,\s]/);
  
  var d = "";
  for (var i = 0, iLen = crp.length; iLen - 2 > i; i+=2) {
    var p = [];
    if ( 0 == i ) {
      p.push( {x: parseFloat(crp[ i ]), y: parseFloat(crp[ i + 1 ])} );
      p.push( {x: parseFloat(crp[ i ]), y: parseFloat(crp[ i + 1 ])} );
      p.push( {x: parseFloat(crp[ i + 2 ]), y: parseFloat(crp[ i + 3 ])} );
      p.push( {x: parseFloat(crp[ i + 4 ]), y: parseFloat(crp[ i + 5 ])} );
    } else if ( iLen - 4 == i ) {
      p.push( {x: parseFloat(crp[ i - 2 ]), y: parseFloat(crp[ i - 1 ])} );
      p.push( {x: parseFloat(crp[ i ]), y: parseFloat(crp[ i + 1 ])} );
      p.push( {x: parseFloat(crp[ i + 2 ]), y: parseFloat(crp[ i + 3 ])} );
      p.push( {x: parseFloat(crp[ i + 2 ]), y: parseFloat(crp[ i + 3 ])} );
    if (i === 0) {
      p.push({ x: crp[i], y: crp[i + 1] })
      p.push({ x: crp[i], y: crp[i + 1] })
      p.push({ x: crp[i + 2], y: crp[i + 3] })
      p.push({ x: crp[i + 4], y: crp[i + 5] })
    } else if (i === iLen - 4) {
      p.push({ x: crp[i - 2], y: crp[i - 1] })
      p.push({ x: crp[i], y: crp[i + 1] })
      p.push({ x: crp[i + 2], y: crp[i + 3] })
      p.push({ x: crp[i + 2], y: crp[i + 3] })
    } else {
      p.push( {x: parseFloat(crp[ i - 2 ]), y: parseFloat(crp[ i - 1 ])} );
      p.push( {x: parseFloat(crp[ i ]), y: parseFloat(crp[ i + 1 ])} );
      p.push( {x: parseFloat(crp[ i + 2 ]), y: parseFloat(crp[ i + 3 ])} );
      p.push( {x: parseFloat(crp[ i + 4 ]), y: parseFloat(crp[ i + 5 ])} );
      p.push({ x: crp[i - 2], y: crp[i - 1] })
      p.push({ x: crp[i], y: crp[i + 1] })
      p.push({ x: crp[i + 2], y: crp[i + 3] })
      p.push({ x: crp[i + 4], y: crp[i + 5] })
    }
    
    // Catmull-Rom to Cubic Bezier conversion matrix 

    // Catmull-Rom to Cubic Bezier conversion matrix
    //    0       1       0       0
    //  -1/6      1      1/6      0
    //    0      1/6      1     -1/6
    //    0       0       1       0

    var bp = [];
    bp.push( { x: p[1].x,  y: p[1].y } );
    bp.push( { x: ((-p[0].x + 6*p[1].x + p[2].x) / 6), y: ((-p[0].y + 6*p[1].y + p[2].y) / 6)} );
    bp.push( { x: ((p[1].x + 6*p[2].x - p[3].x) / 6),  y: ((p[1].y + 6*p[2].y - p[3].y) / 6) } );
    bp.push( { x: p[2].x,  y: p[2].y } );
    let bp = []
    bp.push({ x: p[1].x, y: p[1].y })
    bp.push({ x: ((-p[0].x + 6 * p[1].x + p[2].x) / 6), y: ((-p[0].y + 6 * p[1].y + p[2].y) / 6) })
    bp.push({ x: ((p[1].x + 6 * p[2].x - p[3].x) / 6), y: ((p[1].y + 6 * p[2].y - p[3].y) / 6) })
    bp.push({ x: p[2].x, y: p[2].y })

    d += "C" + bp[1].x + "," + bp[1].y + " " + bp[2].x + "," + bp[2].y + " " + bp[3].x + "," + bp[3].y + " ";
    d += `C${bp[1].x},${bp[1].y} ${bp[2].x},${bp[2].y} ${bp[3].x},${bp[3].y} `
  }
  
  return d;

  return d
}

export default parsePath