come tracciare una curva uniforms attraverso N punti usando la canvas HTML5 javascript?

Per un’applicazione di disegno sto salvando le coordinate del movimento del mouse su un array e quindi disegnandole con lineTo. La linea risultante non è liscia. Come posso produrre una curva singola tra tutti i punti raccolti?

Ho cercato su Google, ma ho trovato solo 3 funzioni per disegnare linee: per 2 punti campione, usa semplicemente lineTo. Per 3 punti campione quadraticCurveTo, per 4 punti campione, bezierCurveTo.

(Ho provato a disegnare un bezierCurveTo per ogni 4 punti dell’array, ma questo porta a punti ogni 4 punti campione, invece di una curva uniforms e continua.)

Come scrivere una funzione per tracciare una curva uniforms con 5 punti campione e oltre?

    Il problema con l’unione dei punti di campionamento successivi con le funzioni di tipo “curveTo” disgiunte, è che dove le curve si incontrano non è regolare. Questo perché le due curve condividono un punto finale ma sono influenzate da punti di controllo completamente disgiunti. Una soluzione è “curvare verso” i punti medi tra i successivi 2 punti di campionamento successivi. Unire le curve usando questi nuovi punti interpolati dà una transizione fluida ai punti finali (ciò che è un punto finale per una iterazione diventa un punto di controllo per la successiva iterazione). In altre parole, le due curve disgiunte hanno molto più in comune ora.

    Questa soluzione è stata estratta dal libro “Foundation ActionScript 3.0 Animation: Making things move”. p.95 – tecniche di rendering: creazione di più curve.

    Nota: questa soluzione in realtà non disegna attraverso ciascuno dei punti, che era il titolo della mia domanda (piuttosto si approssima alla curva attraverso i punti campione ma non passa mai attraverso i punti campione), ma per i miei scopi (un’applicazione di disegno), è abbastanza buono per me e visivamente non puoi dire la differenza. C’è una soluzione per passare attraverso tutti i punti di campionamento, ma è molto più complicata (vedi http://www.cartogrammar.com/blog/actionscript-curves-update/ )

    Ecco il codice di disegno per il metodo di approssimazione:

    // move to the first point ctx.moveTo(points[0].x, points[0].y); for (i = 1; i < points.length - 2; i ++) { var xc = (points[i].x + points[i + 1].x) / 2; var yc = (points[i].y + points[i + 1].y) / 2; ctx.quadraticCurveTo(points[i].x, points[i].y, xc, yc); } // curve through the last two points ctx.quadraticCurveTo(points[i].x, points[i].y, points[i+1].x,points[i+1].y); 

    Un po ‘tardi, ma per la cronaca.

    È ansible ottenere linee morbide utilizzando spline cardinali ( note anche come spline canoniche) per disegnare curve morbide che attraversano i punti.

    Ho realizzato questa funzione per la canvas: è divisa in tre funzioni per aumentare la versatilità. La funzione principale del wrapper si presenta così:

     function drawCurve(ctx, ptsa, tension, isClosed, numOfSegments, showPoints) { showPoints = showPoints ? showPoints : false; ctx.beginPath(); drawLines(ctx, getCurvePoints(ptsa, tension, isClosed, numOfSegments)); if (showPoints) { ctx.stroke(); ctx.beginPath(); for(var i=0;i 

    Per disegnare una curva avere una matrice con x, y punti nell'ordine: x1,y1, x2,y2, ...xn,yn .

    Usalo in questo modo:

     var myPoints = [10,10, 40,30, 100,10]; //minimum two points var tension = 1; drawCurve(ctx, myPoints); //default tension=0.5 drawCurve(ctx, myPoints, tension); 

    La funzione sopra chiama due sotto-funzioni, una per calcolare i punti smussati. Ciò restituisce un array con nuovi punti: questa è la funzione principale che calcola i punti smussati:

     function getCurvePoints(pts, tension, isClosed, numOfSegments) { // use input value if provided, or use a default value tension = (typeof tension != 'undefined') ? tension : 0.5; isClosed = isClosed ? isClosed : false; numOfSegments = numOfSegments ? numOfSegments : 16; var _pts = [], res = [], // clone array x, y, // our x,y coords t1x, t2x, t1y, t2y, // tension vectors c1, c2, c3, c4, // cardinal points st, t, i; // steps based on num. of segments // clone array so we don't change the original // _pts = pts.slice(0); // The algorithm require a previous and next point to the actual point array. // Check if we will draw closed or open curve. // If closed, copy end points to beginning and first points to end // If open, duplicate first points to befinning, end points to end if (isClosed) { _pts.unshift(pts[pts.length - 1]); _pts.unshift(pts[pts.length - 2]); _pts.unshift(pts[pts.length - 1]); _pts.unshift(pts[pts.length - 2]); _pts.push(pts[0]); _pts.push(pts[1]); } else { _pts.unshift(pts[1]); //copy 1. point and insert at beginning _pts.unshift(pts[0]); _pts.push(pts[pts.length - 2]); //copy last point and append _pts.push(pts[pts.length - 1]); } // ok, lets start.. // 1. loop goes through point array // 2. loop goes through each segment between the 2 pts + 1e point before and after for (i=2; i < (_pts.length - 4); i+=2) { for (t=0; t <= numOfSegments; t++) { // calc tension vectors t1x = (_pts[i+2] - _pts[i-2]) * tension; t2x = (_pts[i+4] - _pts[i]) * tension; t1y = (_pts[i+3] - _pts[i-1]) * tension; t2y = (_pts[i+5] - _pts[i+1]) * tension; // calc step st = t / numOfSegments; // calc cardinals c1 = 2 * Math.pow(st, 3) - 3 * Math.pow(st, 2) + 1; c2 = -(2 * Math.pow(st, 3)) + 3 * Math.pow(st, 2); c3 = Math.pow(st, 3) - 2 * Math.pow(st, 2) + st; c4 = Math.pow(st, 3) - Math.pow(st, 2); // calc x and y cords with common control vectors x = c1 * _pts[i] + c2 * _pts[i+2] + c3 * t1x + c4 * t2x; y = c1 * _pts[i+1] + c2 * _pts[i+3] + c3 * t1y + c4 * t2y; //store points in array res.push(x); res.push(y); } } return res; } 

    E per disegnare effettivamente i punti come una curva smussata (o qualsiasi altra linea segmentata purché si disponga di una matrice x, y):

     function drawLines(ctx, pts) { ctx.moveTo(pts[0], pts[1]); for(i=2;i 
     var ctx = document.getElementById("c").getContext("2d"); function drawCurve(ctx, ptsa, tension, isClosed, numOfSegments, showPoints) { ctx.beginPath(); drawLines(ctx, getCurvePoints(ptsa, tension, isClosed, numOfSegments)); if (showPoints) { ctx.beginPath(); for(var i=0;i 
     canvas { border: 1px solid red; } 
      

    La prima risposta non passerà attraverso tutti i punti. Questo grafico attraverserà esattamente tutti i punti e sarà una curva prefetto con i punti come [{x:, y:}] n tali punti.

     var points = [{x:1,y:1},{x:2,y:3},{x:3,y:4},{x:4,y:2},{x:5,y:6}] //took 5 example points ctx.moveTo((points[0].x), points[0].y); for(var i = 0; i < points.length-1; i ++) { var x_mid = (points[i].x + points[i+1].x) / 2; var y_mid = (points[i].y + points[i+1].y) / 2; var cp_x1 = (x_mid + points[i].x) / 2; var cp_y1 = (y_mid + points[i].y) / 2; var cp_x2 = (x_mid + points[i+1].x) / 2; var cp_y2 = (y_mid + points[i+1].y) / 2; ctx.quadraticCurveTo(cp_x1,points[i].y ,x_mid, y_mid); ctx.quadraticCurveTo(cp_x2,points[i+1].y ,points[i+1].x,points[i+1].y); } 

    Come fa notare Daniel Howard , Rob Spencer descrive ciò che vuoi su http://scaledinnovation.com/analytics/splines/aboutSplines.html .

    Ecco una demo intertriggers: http://jsbin.com/ApitIxo/2/

    Qui è come uno snippet nel caso in cui jsbin non funzioni.

     < !DOCTYPE html>    Demo smooth connection   
    Click to build a smooth path. (See Rob Spencer's article)


    Decido di aggiungere, piuttosto che postare la mia soluzione in un altro post. Di seguito sono la soluzione che ho costruito, potrebbe non essere perfetta, ma finora l’output è buono.

    Importante: passerà attraverso tutti i punti!

    Se hai qualche idea, per renderla migliore , per favore condividi me. Grazie.

    Ecco il confronto di prima dopo:

    inserisci la descrizione dell'immagine qui

    Salva questo codice in HTML per provarlo.

     < !DOCTYPE html>   Your browser does not support the HTML5 canvas tag.    

    Ho trovato che funziona bene

     function drawCurve(points, tension) { ctx.beginPath(); ctx.moveTo(points[0].x, points[0].y); var t = (tension != null) ? tension : 1; for (var i = 0; i < points.length - 1; i++) { var p0 = (i > 0) ? points[i - 1] : points[0]; var p1 = points[i]; var p2 = points[i + 1]; var p3 = (i != points.length - 2) ? points[i + 2] : p2; var cp1x = p1.x + (p2.x - p0.x) / 6 * t; var cp1y = p1.y + (p2.y - p0.y) / 6 * t; var cp2x = p2.x - (p3.x - p1.x) / 6 * t; var cp2y = p2.y - (p3.y - p1.y) / 6 * t; ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2.x, p2.y); } ctx.stroke(); } 

    Per aggiungere al metodo delle spline cardinali di K3N e forse affrontare le preoccupazioni di TJ Crowder circa l’immersione delle curve in luoghi fuorvianti, ho inserito il seguente codice nella funzione getCurvePoints() , appena prima res.push(x);

     if ((y < _pts[i+1] && y < _pts[i+3]) || (y > _pts[i+1] && y > _pts[i+3])) { y = (_pts[i+1] + _pts[i+3]) / 2; } if ((x < _pts[i] && x < _pts[i+2]) || (x > _pts[i] && x > _pts[i+2])) { x = (_pts[i] + _pts[i+2]) / 2; } 

    Questo crea efficacemente un rettangolo (invisibile) tra ogni coppia di punti successivi e assicura che la curva rimanga all’interno di questo riquadro di delimitazione, cioè. se un punto sulla curva è sopra / sotto / a sinistra / a destra di entrambi i punti, altera la sua posizione all’interno del riquadro. Qui viene usato il punto medio, ma questo potrebbe essere migliorato, magari usando l’interpolazione lineare.

    Incredibilmente in ritardo, ma ispirato dalla risposta brillantemente semplice di Homan, mi consente di postare una soluzione più generale (generale nel senso che la soluzione di Homan va in crash su array di punti con meno di 3 vertici):

     function smooth(ctx, points) { if(points == undefined || points.length == 0) { return true; } if(points.length == 1) { ctx.moveTo(points[0].x, points[0].y); ctx.lineTo(points[0].x, points[0].y); return true; } if(points.length == 2) { ctx.moveTo(points[0].x, points[0].y); ctx.lineTo(points[1].x, points[1].y); return true; } ctx.moveTo(points[0].x, points[0].y); for (var i = 1; i < points.length - 2; i ++) { var xc = (points[i].x + points[i + 1].x) / 2; var yc = (points[i].y + points[i + 1].y) / 2; ctx.quadraticCurveTo(points[i].x, points[i].y, xc, yc); } ctx.quadraticCurveTo(points[i].x, points[i].y, points[i+1].x, points[i+1].y); }