gpt4 book ai didi

javascript - 如何使用 html canvas 和 javascript 用鼠标绘制平滑的连续线

转载 作者:太空狗 更新时间:2023-10-29 13:37:01 25 4
gpt4 key购买 nike

我正在尝试使用 html5 Canvas 和纯 javascript 创建一个简单的绘图/绘画程序。我已经让它工作正常,但是当绘制和移动鼠标太快时,线断开连接,我最后只得到一行点 - 我怎样才能使它成为一条平滑的连续线?

非常感谢您的建议!我对 JS 很陌生,所以代码示例会非常有用,在此先感谢。

当前的 JS 是:

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>

最佳答案

用鼠标绘制平滑曲线。

遗憾的是,如果您想忠于艺术家的意图,这并不是那么容易。

它涉及记录整个鼠标行程。笔画完成后,将点数减少到细节限制(由艺术家设置),然后对剩余点应用贝塞尔平滑函数。

它可以在绘制笔划时完成,但对于某些设备,如果线条变得很长,这可能会变得太多。当实时显示平滑线时,线细节减少会查看所有点,有些人不喜欢它随着线变长而略微变化的方式。

演示

下面的代码演示了一个我发现有用的解决方案。

  • 使用左键进行平滑绘制,一键松开。
  • 使用右键进行实时平滑绘制(蓝线)。
  • 点击鼠标中键清除。

使用顶部的两个 slider 设置平滑量和细节量。单击鼠标左键拖出一个笔划,显示原始线条。释放鼠标时,线条会被简化、平滑并添加到背景图像中。

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>

关于javascript - 如何使用 html canvas 和 javascript 用鼠标绘制平滑的连续线,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40650306/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com