【问题标题】:Implementing smooth sketching and drawing on the <canvas> element在 <canvas> 元素上实现流畅的素描和绘图
【发布时间】:2012-05-20 23:46:12
【问题描述】:

我正在尝试使用画布创建绘图区域。我在绘制曲线时无法使线条看起来平滑,而且我的算法中的线条粗细也发生了变化,这看起来也很糟糕,因为尺寸也跳得很大,你可以看到尺寸变化的地方。我确实找到了这个link on stackoverflow,但这是针对本机 iPhone 应用程序的,我无法弄清楚。

这是我当前的 JS 代码。这是运行on jsFiddle

var xStart,
xEnd,
yStart,
yEnd,
paint,
ctx;
$(document).ready(function (){

   ctx = $('canvas')[0].getContext("2d");
   ctx.strokeStyle = '#000';
   ctx.lineJoin="round";
   ctx.lineCap="round";
   ctx.lineWidth = 1;


   $('canvas').bind('mousedown mousemove mouseup mouseleave touchstart touchmove touchend', function(e){
        var orig = e.originalEvent;

        if(e.type == 'mousedown'){
            e.preventDefault(); e.stopPropagation();

            xStart = e.clientX - $(this).offset().left;
            yStart = e.clientY - $(this).offset().top;
            xEnd = xStart;
            yEnd = yStart;

            paint = true;
            draw(e.type);

        }else if(e.type == 'mousemove'){
            if(paint==true){
                xEnd = e.clientX - $(this).offset().left;
                yEnd = e.clientY - $(this).offset().top;


               lineThickness = 1 + Math.sqrt((xStart - xEnd) *(xStart-xEnd) + (yStart - yEnd) * (yStart-yEnd))/5;

               if(lineThickness > 10){
                    lineThickness = 10;   
               }

                ctx.lineWidth = lineThickness;
                draw(e.type);
            }
        }else if(e.type == 'mouseup'){
            paint = false;
        }else if(e.type == 'mouseleave'){
            paint = false;
        }else if(e.type == 'touchstart'){
            if(orig.touches.length == 1){
                e.preventDefault(); e.stopPropagation();

                xStart = orig.changedTouches[0].pageX - $(this).offset().left;
                yStart = orig.changedTouches[0].pageY - $(this).offset().top;
                xEnd = xStart;
                yEnd = yStart; 

                paint = true;
                draw(e.type);
            }
        }else if(e.type == 'touchmove'){
            if(orig.touches.length == 1){
                if(paint==true){
                    xEnd = orig.changedTouches[0].pageX - $(this).offset().left;
                    yEnd = orig.changedTouches[0].pageY - $(this).offset().top;


                            lineThickness = 1 + Math.sqrt((xStart - xEnd) *(xStart-xEnd) + (yStart - yEnd) * (yStart-yEnd))/6;
                       if(lineThickness > 10){
                          lineThickness = 10;   
                       }


                      ctx.lineWidth = lineThickness;


                    draw(e.type);
                }
            }
        }else if(e.type == 'touchend'){
            paint = false;
        }

      });
    });


    function draw(event){

    if(event == 'mousedown'){
        ctx.beginPath();
        ctx.moveTo(xStart, yStart);
        ctx.lineTo(xEnd, yEnd);
        ctx.stroke();
    }else if(event == 'mousemove'){
        ctx.beginPath();
        ctx.moveTo(xStart, yStart);
        ctx.lineTo(xEnd, yEnd);
        ctx.stroke();
    }else if(event == 'touchstart'){
        ctx.beginPath();
        ctx.moveTo(xStart, yStart);
        ctx.lineTo(xEnd, yEnd);
        ctx.stroke();
    }else if(event == 'touchmove'){
        ctx.beginPath();
        ctx.moveTo(xStart, yStart);
        ctx.lineTo(xEnd, yEnd);
        ctx.stroke();
    }
    xStart = xEnd;
    yStart = yEnd;                  
}

提前谢谢大家。

如果你画画,这就是它现在的样子。

...这就是我想要实现的目标:

【问题讨论】:

    标签: javascript html canvas drawing paint


    【解决方案1】:

    看看这段代码:

    http://jsfiddle.net/aMmVQ/

    我正在做的是在 mouseDown 上开始一个新的点列表,然后为每个 mousemove 我添加一个点到列表中。一旦我得到足够多的点(6 个左右),我就开始绘制二次曲线,曲线的控制点是当前点和下一个点的平均值。

    drawPoints 是发挥这种魔力的位:

    function drawPoints(ctx, points) {
        // draw a basic circle instead
        if (points.length < 6) {
            var b = points[0];
            ctx.beginPath(), ctx.arc(b.x, b.y, ctx.lineWidth / 2, 0, Math.PI * 2, !0), ctx.closePath(), ctx.fill();
            return
        }
        ctx.beginPath(), ctx.moveTo(points[0].x, points[0].y);
        // draw a bunch of quadratics, using the average of two points as the control point
        for (i = 1; i < points.length - 2; i++) {
            var c = (points[i].x + points[i + 1].x) / 2,
                d = (points[i].y + points[i + 1].y) / 2;
            ctx.quadraticCurveTo(points[i].x, points[i].y, c, d)
        }
        ctx.quadraticCurveTo(points[i].x, points[i].y, points[i + 1].x, points[i + 1].y), ctx.stroke()
    }
    

    【讨论】:

    • 您的代码可以很好地创建平滑线条,但是在您绘制时如何更改线条粗细又名 ctx.lineWidth。我对你的方法的问题是你画了所有的点,一旦你画完所有的点,你就结束了 stroke()。要改变线条的粗细,代码似乎必须是 beginPath() moveTo() quadraticCurveTo() stroke() 全部在循环中,但是当我将它移入它时,它只会做一些时髦的事情。
    • 作为记录,您发布的您说要实现的图像不会改变厚度,只会改变末端的不透明度。
    • “内存画布”有什么用?
    【解决方案2】:

    看来您需要在画布中使用一些画笔。很难说你到底需要什么样的画笔,但是很多JS库已经实现了画笔技术。

    例如你看过这个库吗?

    Laso in web 你可以找到许多在Mr. Doob Harmony 项目中实现的画笔。例如stringyHarmony-Brushes github 上的项目。

    【讨论】:

    • 这是上面 jsFiddle link 上的代码,你会看到它真的很不稳定,线条不平滑,你可以清楚地看出笔划中线条粗细的变化
    【解决方案3】:

    我不久前做了一个类似的东西并将它变成了一个 jquery 插件。 看看这里,如果这是你想要的,我会发布更详细的答案并从我的档案中挖掘出简化的 jquery 版本:

    http://jsfiddle.net/95tft/

    编辑

    好的,抱歉我昨天不能这样做:

    最初,上面的代码是从 Doob 先生的“和谐”草图中分叉出来的: http://mrdoob.com/projects/harmony/#ribbon

    (我认为这是最好的解决方案)。 但我有点把它拆了,并在另一个项目中为我自己的目的重新制作它。我已经破解了我自己的插件,让它在这里更容易一点:

    http://jsfiddle.net/dh3bj/

    您可能想要更改的唯一一件事就是将其更改为在 mousedown/mouseup 上工作,这应该很容易也看看插件底部的设置,您应该能够获得您想要的效果使用画笔大小、颜色、alpha (rgba) 等。

    希望有帮助

    【讨论】:

    • 是的,这绝对是正确的方向。感谢您挖掘您的代码,我将等待您的详细答案。
    • @ryuutatsuo 添加在上面。如果您需要更多信息,请告诉我?
    • 如果您在 mrdoob.com 上单击“关于”,其中一个链接是“源代码”,它会将您带到github.com/mrdoob/harmony
    【解决方案4】:

    建议使用围绕已填充曲线的贝塞尔曲线链进行渲染。 (即以 ctx.fill 结尾) 还有很多工作要做,但希望这会有所帮助。

    为贝塞尔曲线改编了一个不错的演示应用

    将它添加到你的小提琴的一个叉子上 http://jsfiddle.net/d3zFU/1/

    代码是

    /*
     * Canvas curves example
     *
     * By Craig Buckler,        http://twitter.com/craigbuckler
     * of OptimalWorks.net        http://optimalworks.net/
     * for SitePoint.com        http://sitepoint.com/
     *
     * Refer to:
     * http://blogs.sitepoint.com/html5-canvas-draw-quadratic-curves/
     * http://blogs.sitepoint.com/html5-canvas-draw-bezier-curves/
     *
     * This code can be used without restriction.
     */
    

    (函数() {

    var canvas, ctx, code, point, style, drag = null, dPoint;
    
    // define initial points
    function Init(quadratic) {
    
        point = {
            p1: { x:100, y:250 },
            p2: { x:400, y:250 }
        };
    
        if (quadratic) {
            point.cp1 = { x: 250, y: 100 };
        }
        else {
            point.cp1 = { x: 150, y: 100 };
            point.cp2 = { x: 350, y: 100 };
        }
    
        // default styles
        style = {
            curve:    { width: 6, color: "#333" },
            cpline:    { width: 1, color: "#C00" },
            point: { radius: 10, width: 2, color: "#900", fill: "rgba(200,200,200,0.5)", arc1: 0, arc2: 2 * Math.PI }
        }
    
        // line style defaults
        ctx.lineCap = "round";
        ctx.lineJoin = "round";
    
        // event handlers
        canvas.onmousedown = DragStart;
        canvas.onmousemove = Dragging;
        canvas.onmouseup = canvas.onmouseout = DragEnd;
    
        DrawCanvas();
    }
    
    
    // draw canvas
    function DrawCanvas() {
        ctx.clearRect(0, 0, canvas.width, canvas.height);
    
        // control lines
        ctx.lineWidth = style.cpline.width;
        ctx.strokeStyle = style.cpline.color;
        ctx.fillStyle = style.cpline.color;
        ctx.beginPath();
        ctx.moveTo(point.p1.x, point.p1.y);
        ctx.lineTo(point.cp1.x, point.cp1.y);
        if (point.cp2) {
            ctx.moveTo(point.p2.x, point.p2.y);
            ctx.lineTo(point.cp2.x, point.cp2.y);
        }
        else {
            ctx.lineTo(point.p2.x, point.p2.y);
        }
        ctx.stroke();
    
        // curve
    ctx.lineWidth = 1 ; //style.curve.width;
        ctx.strokeStyle = style.curve.color;
        ctx.beginPath();
        ctx.moveTo(point.p1.x, point.p1.y);
        if (point.cp2) {
            ctx.bezierCurveTo(point.cp1.x, point.cp1.y, point.cp2.x, point.cp2.y, point.p2.x, point.p2.y);
            ctx.bezierCurveTo(point.cp2.x, point.cp2.y+12, point.cp1.x, point.cp1.y+12, point.p1.x, point.p1.y);
    
        }
        else {
            ctx.quadraticCurveTo(point.cp1.x, point.cp1.y, point.p2.x, point.p2.y);
        }
    //ctx.stroke();
    ctx.fill();
    
        // control points
        for (var p in point) {
            ctx.lineWidth = style.point.width;
            ctx.strokeStyle = style.point.color;
            ctx.fillStyle = style.point.fill;
            ctx.beginPath();
            ctx.arc(point[p].x, point[p].y, style.point.radius, style.point.arc1, style.point.arc2, true);
            ctx.fill();
            ctx.stroke();
        }
    
        ShowCode();
    }
    
    
    // show canvas code
    function ShowCode() {
        if (code) {
            code.firstChild.nodeValue =
                "canvas = document.getElementById(\"canvas\");\n"+
                "ctx = canvas.getContext(\"2d\")\n"+
                "ctx.lineWidth = " + style.curve.width +
                ";\nctx.strokeStyle = \"" + style.curve.color +
                "\";\nctx.beginPath();\n" +
                "ctx.moveTo(" + point.p1.x + ", " + point.p1.y +");\n" +
                (point.cp2 ?
                    "ctx.bezierCurveTo("+point.cp1.x+", "+point.cp1.y+", "+point.cp2.x+", "+point.cp2.y+", "+point.p2.x+", "+point.p2.y+");" :
                    "ctx.quadraticCurveTo("+point.cp1.x+", "+point.cp1.y+", "+point.p2.x+", "+point.p2.y+");"
                ) +
                "\nctx.stroke();"
            ;
        }
    }
    
    
    // start dragging
    function DragStart(e) {
        e = MousePos(e);
        var dx, dy;
        for (var p in point) {
            dx = point[p].x - e.x;
            dy = point[p].y - e.y;
            if ((dx * dx) + (dy * dy) < style.point.radius * style.point.radius) {
                drag = p;
                dPoint = e;
                canvas.style.cursor = "move";
                return;
            }
        }
    }
    
    
    // dragging
    function Dragging(e) {
        if (drag) {
            e = MousePos(e);
            point[drag].x += e.x - dPoint.x;
            point[drag].y += e.y - dPoint.y;
            dPoint = e;
            DrawCanvas();
        }
    }
    
    
    // end dragging
    function DragEnd(e) {
        drag = null;
        canvas.style.cursor = "default";
        DrawCanvas();
    }
    
    
    // event parser
    function MousePos(event) {
        event = (event ? event : window.event);
        return {
            x: event.pageX - canvas.offsetLeft,
            y: event.pageY - canvas.offsetTop
        }
    }
    
    
    // start
    canvas = document.getElementById("canvas");
    code = document.getElementById("code");
    if (canvas.getContext) {
        ctx = canvas.getContext("2d");
        Init(canvas.className == "quadratic");
    }
    

    })();

    【讨论】:

      【解决方案5】:

      对于那些对@Alex 提供的代码的点击版本感兴趣的人,我在这里重写了他的脚本:

      http://jsbin.com/aqoqad/3/

      【讨论】:

        【解决方案6】:

        你为什么不用croquis.js

        它有整洁的brush implementation like photoshop :)

        这里是Demo,它正在使用 croquis.js。

        【讨论】:

        • 我能得到这个的源代码吗? Croquis 没有文档,我需要找到如何在每次鼠标移动时绘制一个圆圈。
        【解决方案7】:

        重要!

        我收集了一些需要的零件并附在此处!

        <canvas id="paint_board" width="500" height="800" style="border: 1px solid;"></canvas>
        
        <script>
        var el = document.getElementById('paint_board');
        // rect gets the cavas left top value in browser
        var rect = el.getBoundingClientRect();
        var ctx = el.getContext('2d');
            ctx.lineJoin = ctx.lineCap = 'round';
            ctx.lineWidth = 1;
        var isDrawing, pen_type=1;
        var screenWidth=500, screenHeight=500;  //set canvas width and height
        var strokes=20;                         //how many strokes to draw
        var color = [0, 0, 0]; // color val RGB 0-255, 0-255, 0-255
        var painters = [], unpainters = [], timers = [];
        var brushPressure=1; // brush Opacity
        var easing = 0.7; // kind of "how loopy" higher= bigger loops
        var refreshRate = 30; // set this higher if performace is an issue directly affects easing
        var mouseX = screenWidth / 2, mouseY = screenHeight / 2;
        var testinterval;
        
        pen_init();
        function pen_init(){
            for(var i = 0; i < strokes; i++) {
                var ease = Math.random() * 0.05 + easing;
                painters.push({
                    dx : screenWidth / 2,
                    dy : screenHeight / 2,
                    ax : 0,
                    ay : 0,
                    div : 0.1,
                    ease : ease
                });
            }
            testinterval = setInterval(update, refreshRate);
            function update() {
                var i;
                ctx.strokeStyle = "rgba(" + color[0] + ", " + color[1] + ", " + color[2] + ", " + brushPressure + ")";
                for( i = 0; i < painters.length; i++) {
                    ctx.beginPath();
                    var dx = painters[i].dx;
                    var dy = painters[i].dy;
                    ctx.moveTo(dx, dy);
                    var dx1 = painters[i].ax = (painters[i].ax + (painters[i].dx - mouseX) * painters[i].div) * painters[i].ease;
                    painters[i].dx -= dx1;
                    var dx2 = painters[i].dx;
                    var dy1 = painters[i].ay = (painters[i].ay + (painters[i].dy - mouseY) * painters[i].div) * painters[i].ease;
                    painters[i].dy -= dy1;
                    var dy2 = painters[i].dy;
                    ctx.lineTo(dx2, dy2);
                    ctx.stroke();
                }
            }
        }
        el.onmousedown = function(e) {
            isDrawing = true;
            mouseX = e.clientX+window.scrollX-rect.left;
            mouseY = e.clientY+window.scrollY-rect.top;
            var i = 0, paintersLen = painters.length;
            for(i; i < paintersLen; i++) {
                        painters[i].dx = mouseX;
                        painters[i].dy = mouseY;
            }
        };
        el.onmousemove = function(e) {
            if (!isDrawing) return;
            mouseX = e.clientX+window.scrollX-rect.left;
            mouseY = e.clientY+window.scrollY-rect.top;
        };
        el.onmouseup = function() {
            isDrawing = false;
        };
        </script>
        

        只需复制所有内容并将其粘贴到您的代码中即可。并且不要忘记点击投票!

        【讨论】:

        • 只发布代码不是正确的答案。请解释一下。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2019-05-10
        • 2017-10-06
        • 2011-05-12
        • 2023-03-23
        • 1970-01-01
        • 1970-01-01
        • 2015-07-21
        相关资源
        最近更新 更多