javascript - एचटीएमएल कैनवास और जावास्क्रिप्ट का प्रयोग करके माउस के साथ चिकनी निरंतर रेखा कैसे खींचना है




html5 canvas (2)

मैं html5 कैनवास और सादे जावास्क्रिप्ट का उपयोग कर एक सरल ड्रा / पेंट प्रोग्राम बनाने का प्रयास कर रहा हूं। मुझे ठीक से काम करना पड़ता है, लेकिन जब रेखा खींचती है और लाइन को डिस्कनेक्ट करने में तेज़ हो जाता है और मैं बस डॉट्स की एक पंक्ति के साथ समाप्त होता हूं - मैं यह कैसे एक चिकनी निरंतर रेखा बना सकता हूँ?

सलाह बहुत सराहना की जाएगी! मैं जे एस के लिए काफी नया हूँ इसलिए कोड उदाहरण वाकई उपयोगी होगा, धन्यवाद अग्रिम में

वर्तमान जेएस है:

var canvas, ctx
var mouseX, mouseY, mouseDown = 0

function draw(ctx,x,y,size) {
  ctx.fillStyle = "#000000"
  ctx.beginPath()
  ctx.arc(x, y, size, 0, Math.PI*2, true)
  ctx.closePath()
  ctx.fill()
}

function clearCanvas(canvas,ctx) {
  ctx.clearRect(0, 0, canvas.width, canvas.height)
}

function onMouseDown() {
  mouseDown = 1
  draw(ctx, mouseX, mouseY, 2)
}

function onMouseUp() {
  mouseDown = 0
}

function onMouseMove(e) {
  getMousePos(e)
  if (mouseDown == 1) {
      draw(ctx, mouseX, mouseY, 2)
  }
}

function getMousePos(e) {
  if (!e)
      var e = event
  if (e.offsetX) {
      mouseX = e.offsetX
      mouseY = e.offsetY
  }
  else if (e.layerX) {
      mouseX = e.layerX
      mouseY = e.layerY
  }
 }

function init() {
    canvas = document.getElementById('sketchpad')
    ctx = canvas.getContext('2d')
    canvas.addEventListener('mousedown', onMouseDown, false)
    canvas.addEventListener('mousemove', onMouseMove, false)
    window.addEventListener('mouseup', onMouseUp, false)
}
init();
<canvas id="sketchpad" width="500" height="500"></canvas>


माउस के साथ एक चिकनी वक्र आरेखण।

अफसोस की बात यह आसान नहीं है कि यदि आप कलाकारों के लिए सही बने रहना चाहते हैं

इसमें पूरे माउस स्ट्रोक को रिकॉर्ड करना शामिल है। स्ट्रोक पूर्ण होने पर, बिंदुओं की संख्या को विस्तार सीमा (कलाकार द्वारा निर्धारित) में कम करने के बाद शेष बिंदुओं पर एक बीज़ियर चौरसाई फंक्शन लागू करें।

यह स्ट्रोक के रूप में किया जा सकता है लेकिन कुछ उपकरणों के लिए यह बहुत अधिक हो सकता है अगर लाइन बहुत लंबे हो जाती है चूंकि लाइन विवरण में कमी सभी बिंदुओं पर दिखती है जब चिकनी रेखा को दिखाते हुए कुछ लोगों को ऐसा नहीं लगता कि जैसे-जैसे लाइन बहुत कम हो जाती है, जैसे लाइन लंबे समय तक बढ़ जाती है

डेमो

नीचे दिए गए कोड में एक समाधान का पता चलता है जो मुझे उपयोगी पाया गया है।

  • एक बटन रिलीज के साथ चौरसाई करने के लिए बाईं बटन का उपयोग करें
  • लाइव स्माउटिंग (नीली रेखा) के साथ आने के लिए दाएं बटन का उपयोग करें।
  • मध्य माउस बटन साफ़ करने के लिए क्लिक करें

चौरसाई की मात्रा निर्धारित करने के लिए शीर्ष पर दो स्लाइडर्स का उपयोग करें, और विस्तार की मात्रा। एक स्ट्रोक को खींचने के लिए वाम क्लिक करें, कच्ची रेखा दिखाई जाती है। जब माउस को रिलीज़ किया जाता है, तो रेखा को सरलीकृत किया जाता है, चिकना पड़ता है, और पृष्ठभूमि छवि में जोड़ा जाता है।

var canvas = document.getElementById("canV"); 
var ctx = canvas.getContext("2d");


// mouse stuff
var mouse = {
    x:0,
    y:0,
    buttonLastRaw:0, // user modified value 
    buttonRaw:0,
    buttons:[1,2,4,6,5,3], // masks for setting and clearing button raw bits;
};
function mouseMove(event){
    mouse.x = event.offsetX;  mouse.y = event.offsetY; 
    if(mouse.x === undefined){ mouse.x = event.clientX;  mouse.y = event.clientY;}    
    if(event.type === "mousedown"){ mouse.buttonRaw |= mouse.buttons[event.which-1];
    }else if(event.type === "mouseup"){mouse.buttonRaw &= mouse.buttons[event.which+2];
    }else if(event.type === "mouseout"){ mouse.buttonRaw = 0; mouse.over = false;
    }else if(event.type === "mouseover"){ mouse.over = true; }
    event.preventDefault();
}
canvas.addEventListener('mousemove',mouseMove);
canvas.addEventListener('mousedown',mouseMove);
canvas.addEventListener('mouseup'  ,mouseMove); 
canvas.addEventListener('mouseout'  ,mouseMove); 
canvas.addEventListener('mouseover'  ,mouseMove); 
canvas.addEventListener("contextmenu", function(e){ e.preventDefault();}, false);


// Line simplification based on
// the Ramer–Douglas–Peucker algorithm
// referance https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
// points are and array of arrays consisting of [[x,y],[x,y],...,[x,y]]
// length is in pixels and is the square of the actual distance.
// returns array of points of the same form as the input argument points.
var simplifyLineRDP = function(points, length) {
    var simplify = function(start, end) { // recursize simplifies points from start to end
        var maxDist, index, i, xx , yy, dx, dy, ddx, ddy, p1, p2, p, t, dist, dist1;
        p1 = points[start];
        p2 = points[end];   
        xx = p1[0];
        yy = p1[1];
        ddx = p2[0] - xx;
        ddy = p2[1] - yy;
        dist1 = (ddx * ddx + ddy * ddy);
        maxDist = length;
        for (var i = start + 1; i < end; i++) {
            p = points[i];
            if (ddx !== 0 || ddy !== 0) {
                t = ((p[0] - xx) * ddx + (p[1] - yy) * ddy) / dist1;
                if (t > 1) {
                    dx = p[0] - p2[0];
                    dy = p[1] - p2[1];
                } else 
                if (t > 0) {
                    dx = p[0] - (xx + ddx * t);
                    dy = p[1] - (yy + ddy * t);
                } else {
                    dx = p[0] - xx;
                    dy = p[1] - yy;
                }
            }else{
                dx = p[0] - xx;
                dy = p[1] - yy;
            }
            dist = dx * dx + dy * dy 
            if (dist > maxDist) {
                index = i;
                maxDist = dist;
            }
        }

        if (maxDist > length) { // continue simplification while maxDist > length
            if (index - start > 1){
                simplify(start, index);
            }
            newLine.push(points[index]);
            if (end - index > 1){
                simplify(index, end);
            }
        }
    }    
    var end = points.length - 1;
    var newLine = [points[0]];
    simplify(0, end);
    newLine.push(points[end]);
    return newLine;
}



// This is my own smoothing method 
// It creates a set of bezier control points either 2nd order or third order 
// bezier curves.
// points: list of points
// cornerThres: when to smooth corners and represents the angle between to lines. 
//     When the angle is smaller than the cornerThres then smooth.
// match: if true then the control points will be balanced.
// Function will make a copy of the points

var smoothLine = function(points,cornerThres,match){  // adds bezier control points at points if lines have angle less than thres
    var  p1, p2, p3, dist1, dist2, x, y, endP, len, angle, i, newPoints, aLen, closed, bal, cont1, nx1, nx2, ny1, ny2, np;
    function dot(x, y, xx, yy) {  // get do product
        // dist1,dist2,nx1,nx2,ny1,ny2 are the length and  normals and used outside function
        // normalise both vectors
        dist1 = Math.sqrt(x * x + y * y); // get length
        if (dist1  > 0) {  // normalise
            nx1 = x / dist1 ;
            ny1 = y / dist1 ;
        }else {
            nx1 = 1;  // need to have something so this will do as good as anything
            ny1 = 0;
        }
        dist2  = Math.sqrt(xx * xx + yy * yy);
        if (dist2  > 0) {
            nx2 = xx / dist2;
            ny2 = yy / dist2;
        }else {
            nx2 = 1;
            ny2 = 0;
        }
       return Math.acos(nx1 * nx2 + ny1 * ny2 ); // dot product
    }
    newPoints = []; // array for new points
    aLen = points.length;
    if(aLen <= 2){  // nothing to if line too short
        for(i = 0; i < aLen; i ++){  // ensure that the points are copied          
            newPoints.push([points[i][0],points[i][1]]);
        }
        return newPoints;
    }
    p1 = points[0];
    endP =points[aLen-1];
    i = 0;  // start from second poitn if line not closed
    closed = false;
    len = Math.hypot(p1[0]- endP[0], p1[1]-endP[1]);
    if(len < Math.SQRT2){  // end points are the same. Join them in coordinate space
        endP =  p1;
        i = 0;             // start from first point if line closed
        p1 = points[aLen-2];
        closed = true;
    }       
    newPoints.push([points[i][0],points[i][1]])
    for(; i < aLen-1; i++){
        p2 = points[i];
        p3 = points[i + 1];
        angle = Math.abs(dot(p2[0] - p1[0], p2[1] - p1[1], p3[0] - p2[0], p3[1] - p2[1]));
        if(dist1 !== 0){  // dist1 and dist2 come from dot function
            if( angle < cornerThres*3.14){ // bend it if angle between lines is small
                  if(match){
                      dist1 = Math.min(dist1,dist2);
                      dist2 = dist1;
                  }
                  // use the two normalized vectors along the lines to create the tangent vector
                  x = (nx1 + nx2) / 2;  
                  y = (ny1 + ny2) / 2;
                  len = Math.sqrt(x * x + y * y);  // normalise the tangent
                  if(len === 0){
                      newPoints.push([p2[0],p2[1]]);                                  
                  }else{
                      x /= len;
                      y /= len;
                      if(newPoints.length > 0){
                          var np = newPoints[newPoints.length-1];
                          np.push(p2[0]-x*dist1*0.25);
                          np.push(p2[1]-y*dist1*0.25);
                      }
                      newPoints.push([  // create the new point with the new bezier control points.
                            p2[0],
                            p2[1],
                            p2[0]+x*dist2*0.25,
                            p2[1]+y*dist2*0.25
                      ]);
                  }
            }else{
                newPoints.push([p2[0],p2[1]]);            
            }
        }
        p1 = p2;
    }  
    if(closed){ // if closed then copy first point to last.
        p1 = [];
        for(i = 0; i < newPoints[0].length; i++){
            p1.push(newPoints[0][i]);
        }
        newPoints.push(p1);
    }else{
        newPoints.push([points[points.length-1][0],points[points.length-1][1]]);      
    }
    return newPoints;    
}

// creates a drawable image
var createImage = function(w,h){
    var image = document.createElement("canvas");
    image.width = w;
    image.height =h; 
    image.ctx = image.getContext("2d"); 
    return image;
}  

// draws the smoothed line with bezier control points.
var drawSmoothedLine = function(line){
    var i,p;
    ctx.beginPath()
    ctx.moveTo(line[0][0],line[0][1])
    for(i = 0; i < line.length-1; i++){
       p = line[i];
       p1 = line[i+1]
       if(p.length === 2){ // linear 
            ctx.lineTo(p[0],p[1])
       }else
       if(p.length === 4){ // bezier 2nd order
           ctx.quadraticCurveTo(p[2],p[3],p1[0],p1[1]);
       }else{              // bezier 3rd order
           ctx.bezierCurveTo(p[2],p[3],p[4],p[5],p1[0],p1[1]);
       }
    }
    if(p.length === 2){
        ctx.lineTo(p1[0],p1[1])
    }
    ctx.stroke();
}

// smoothing settings
var liveSmooth;
var lineSmooth = {};
lineSmooth.lengthMin = 8;  // square of the pixel length
lineSmooth.angle = 0.8;      // angle threshold
lineSmooth.match = false;  // not working.
// back buffer to save the canvas allowing the new line to be erased
var backBuffer = createImage(canvas.width,canvas.height);
var currentLine = [];
mouse.lastButtonRaw = 0;  // add mouse last incase not there
ctx.lineWidth = 3;
ctx.lineJoin = "round";
ctx.lineCap = "round";
ctx.strokeStyle = "black";
ctx.clearRect(0,0,canvas.width,canvas.height);
var drawing = false;  // if drawing
var input = false;  // if menu input
var smoothIt = false;  // flag to allow feedback that smoothing is happening as it takes some time.
function draw(){
    // if not drawing test for menu interaction and draw the menus
    if(!drawing){      
        if(mouse.x < 203 && mouse.y < 24){
            if(mouse.y < 13){
                if(mouse.buttonRaw === 1){
                    ctx.clearRect(3,3,200,10);
                    lineSmooth.angle = (mouse.x-3)/200;
                    input = true;
                }
            }else
            if(mouse.buttonRaw === 1){
                ctx.clearRect(3,14,200,10);
                lineSmooth.lengthMin = (mouse.x-3)/10;
                input = true;
            }
                
            canvas.style.cursor = "pointer";
        }else{
            canvas.style.cursor = "crosshair";
            
        }
        if(mouse.buttonRaw === 0 && input){
            input = false;
            mouse.lastButtonRaw = 0;
        }
        ctx.lineWidth = 0.5;
        ctx.fillStyle = "red";
        ctx.clearRect(3,3,200,10);
        ctx.clearRect(3,14,200,10);
        ctx.fillRect(3,3,lineSmooth.angle*200,10);
        ctx.fillRect(3,14,lineSmooth.lengthMin*10,10);

        ctx.textAlign = "left";
        ctx.textBaseline = "top";
        ctx.fillStyle = "#000"
        ctx.strokeRect(3,3,200,10);
        ctx.fillText("Smooth "+(lineSmooth.angle * (180 / Math.PI)).toFixed(0)+"deg",5,2)
        ctx.strokeRect(3,14,200,10);
        ctx.fillText("Detail "+lineSmooth.lengthMin.toFixed(0) + "pixels",5,13);

    }else{
        canvas.style.cursor = "crosshair"; 
    }
    if(!input){
         ctx.lineWidth = 3;
        if(mouse.buttonRaw === 4 && mouse.lastButtonRaw === 0){
            currentLine = [];
            drawing  = true;

            backBuffer.ctx.clearRect(0,0,canvas.width,canvas.height);
            backBuffer.ctx.drawImage(canvas,0,0);
            currentLine.push([mouse.x,mouse.y])
        }else
        if(mouse.buttonRaw === 4){
            var lp = currentLine[currentLine.length-1]; // get last point
            // dont record point if no movement
            if(mouse.x !== lp[0] || mouse.y !== lp[1] ){
                currentLine.push([mouse.x,mouse.y]);
                ctx.beginPath();
                ctx.moveTo(lp[0],lp[1])
                ctx.lineTo(mouse.x,mouse.y);
                ctx.stroke();
                liveSmooth = smoothLine(
                    simplifyLineRDP(
                        currentLine,
                        lineSmooth.lengthMin
                    ),
                    lineSmooth.angle,
                    lineSmooth.match
                );
                ctx.clearRect(0,0,canvas.width,canvas.height);
               ctx.drawImage(backBuffer,0,0);
                ctx.strokeStyle = "Blue";
                drawSmoothedLine(liveSmooth );
                ctx.strokeStyle = "black";
            }
        }else
        if(mouse.buttonRaw === 0 && mouse.lastButtonRaw === 4){
            ctx.textAlign = "center"
            ctx.fillStyle = "red"
            ctx.fillText("Smoothing...",canvas.width/2,canvas.height/5);
            smoothIt = true;
        }else
        if(smoothIt){
            smoothIt = false;
            
            var newLine = smoothLine(
                simplifyLineRDP(
                    currentLine,
                    lineSmooth.lengthMin
                ),
                lineSmooth.angle,
                lineSmooth.match
            );
            ctx.clearRect(0,0,canvas.width,canvas.height);
            ctx.drawImage(backBuffer,0,0);
            drawSmoothedLine(newLine);
            drawing  = false;
            
        }


        if(mouse.buttonRaw === 1 && mouse.lastButtonRaw === 0){
            currentLine = [];
            drawing  = true;

            backBuffer.ctx.clearRect(0,0,canvas.width,canvas.height);
            backBuffer.ctx.drawImage(canvas,0,0);
            currentLine.push([mouse.x,mouse.y])
        }else
        if(mouse.buttonRaw === 1){
            var lp = currentLine[currentLine.length-1]; // get last point
            // dont record point if no movement
            if(mouse.x !== lp[0] || mouse.y !== lp[1] ){
                currentLine.push([mouse.x,mouse.y]);
                ctx.beginPath();
                ctx.moveTo(lp[0],lp[1])
                ctx.lineTo(mouse.x,mouse.y);
                ctx.stroke();
            }
        }else
        if(mouse.buttonRaw === 0 && mouse.lastButtonRaw === 1){
            ctx.textAlign = "center"
            ctx.fillStyle = "red"
            ctx.fillText("Smoothing...",canvas.width/2,canvas.height/5);
            smoothIt = true;
        }else
        if(smoothIt){
            smoothIt = false;
            
            var newLine = smoothLine(
                simplifyLineRDP(
                    currentLine,
                    lineSmooth.lengthMin
                ),
                lineSmooth.angle,
                lineSmooth.match
            );
            ctx.clearRect(0,0,canvas.width,canvas.height);
            ctx.drawImage(backBuffer,0,0);
            drawSmoothedLine(newLine);
            drawing  = false;
            
        }
    }
    // middle button clear
    if(mouse.buttonRaw === 2){
        ctx.clearRect(0,0,canvas.width,canvas.height);
    }
    mouse.lastButtonRaw = mouse.buttonRaw;

    requestAnimationFrame(draw);

}

draw();
.canC { width:1000px;  height:500px; border:1px black solid;}
<canvas class="canC" id="canV" width=1000 height=500></canvas>


अच्छा प्रश्न! और अधिक कैनवास एपीआई जानने के लिए मैं आपको एक साइट https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API की सलाह देता हूं।

मुझे लगता है कि lineTo का प्रयोग arc से बेहतर है। इसलिए मुझे आशा है कि यह कोड आपकी सहायता करेगा।

var canvas, ctx;
var mouseDown = 0, lastX, lastY;



function draw(ctx,x,y) {
  ctx.beginPath();
  ctx.moveTo(lastX,lastY);
  ctx.lineTo(x,y);
  ctx.closePath();
  ctx.stroke();
}

function clearCanvas(canvas,ctx) {
  ctx.clearRect(0, 0, canvas.width, canvas.height)
}

function onMouseDown(e) {
  var xy = getMousePos(e);
  lastX = xy.mouseX;
  lastY = xy.mouseY;
  mouseDown = 1;
}

function onMouseUp() {
  mouseDown = 0
}

function onMouseMove(e) {
  if (mouseDown == 1) {
      var xy = getMousePos(e);
      draw(ctx, xy.mouseX, xy.mouseY);
      lastX = xy.mouseX, lastY = xy.mouseY;
  }
}

function getMousePos(e) {
    var o = {};
  if (!e)
      var e = event
  if (e.offsetX) {
      o.mouseX = e.offsetX
      o.mouseY = e.offsetY
  }
  else if (e.layerX) {
      o.mouseX = e.layerX
      o.mouseY = e.layerY
  }
  return o;
 }

function init() {
    canvas = document.getElementById('sketchpad')
    ctx = canvas.getContext('2d')
    canvas.addEventListener('mousedown', onMouseDown, false)
    canvas.addEventListener('mousemove', onMouseMove, false)
    canvas.addEventListener('mouseup', onMouseUp, false)
}
init();





canvas