加速HTMLM Canvas画笔onMouseMove

时间:2014-02-21 09:38:59

标签: javascript html5 canvas

我目前正在尝试创建在Canvas HTML-Element上绘制的画笔。它们最终应该像Photoshop-Brushes一样工作:点击 - 移动 - 沿着鼠标移动释放结果。

我已经实现了一个非常原始的画笔,但是这很快变得非常慢,here is a JSFiddle。尝试绘制多个笔划或很长的笔划,您会看到捕获点的距离变得更大。可能这种行为是通过清除整个画布并迭代所有保存的点而产生的,但我不太确定什么是更好的方法。这是主要的绘图代码:

// while moving the mouse, each captured point gets saved into
// a multi-dimensional array of strokes; then, at each event
// (mousemove, mousedown and mouseup) this function is called

PrimitiveBrush.prototype._draw = function () {
    this.ctx.clearRect(0, 0, this.ctx.width, this.ctx.height)

    for (var i = 0, l = this.strokes.length; i < l; i++) {
        this.ctx.moveTo(this.strokes[i][0].x, this.strokes[i][0].y);
        for (var j = 1, m = this.strokes[i].length; j < m; j++) {
            var point = this.strokes[i][j];
            this.ctx.lineTo(point.x, point.y);
        }
    }

    this.ctx.stroke();
};

有什么想法吗?

2 个答案:

答案 0 :(得分:2)

我看到你是从mousemove缓存点并在requestAnimationFrame中绘制它们。

尼斯!

将鼠标移动捕捉与绘图分开,从而使绘图与屏幕刷新很好地协调。

有人建议重构:

  • 不要每次抽奖都清除画布
  • 使用beginPath避免重复过度绘制现有细分
  • 仅绘制最近添加的点数

因此,不要在this.strokes中绘制每个点数组,只需绘制最后一个点数组。

为了获得更好的性能,只需在最后一个点数组中绘制未绘制的点。

(可选)(在此结束时):清除画布并重新绘制所有点,以获得更清晰的最终绘图。

示例代码和演示:http://jsfiddle.net/m1erickson/wAGcL/

<!doctype html>
<html>
<head>
<link rel="stylesheet" type="text/css" media="all" href="css/reset.css" /> <!-- reset css -->
<script type="text/javascript" src="http://code.jquery.com/jquery.min.js"></script>
<style>
    body{ background-color: ivory; }
    canvas{border:1px solid red;}
</style>
<script>
$(function(){

    // get reference to canvas and save canvas offsets
    var canvas = document.getElementById('drawing');
    var offsetX=canvas.offsetLeft;
    var offsetY=canvas.offsetTop;

    var requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame || window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;

    /**
     * @param {CanvasRenderingContext2D} context
     */
    function PrimitiveBrush(context) {
        if (!(context instanceof CanvasRenderingContext2D)) {
            throw new Error('No 2D rendering context given!');
        }

        this.ctx = context;
        this.strokes = [];
        this.strokeIndex=0;
        this.workingStrokes=[];
        this.lastLength=0;
        this.isTouching = false;

        // init context
        this.ctx.strokeStyle = '#f00';
        this.ctx.lineWidth = '3';
        this.ctx.lineCap = this.ctx.lineJoin = 'round';

        // start the drawing loop
        this._draw();
    }

    /**
     * Begins a new stroke
     * @param  {MouseEvent} event
     */
    PrimitiveBrush.prototype.start = function (event) {
        var x=event.clientX-offsetX;
        var y=event.clientY-offsetY;
        this.workingStrokes=[{x:x,y:y}];
        this.strokes.push(this.workingStrokes);
        this.lastLength=1;
        this.isTouching = true;
    };

    /**
     * Moves the current position of our brush
     * @param  {MouseEvent} event
     */
    PrimitiveBrush.prototype.move = function (event) {
        if(!this.isTouching){return;}
        var x=event.clientX-offsetX;
        var y=event.clientY-offsetY;
        this.workingStrokes.push({x:x,y:y});
    };

    /**
     * Stops a stroke
     * @param  {MouseEvent} event
     */
    PrimitiveBrush.prototype.end = function (event, foo) {
        this.move(event);
        this.isTouching = false;
    };

    PrimitiveBrush.prototype._draw = function () {

        requestAnimationFrame(this._draw.bind(this));

        // save the current length quickly (it's dynamic)
        var length=this.workingStrokes.length;

        // return if there's no work to do
        if(length<=this.lastLength){return;}

        var startIndex=this.lastLength-1;

        this.lastLength=length;

        var pt0=this.workingStrokes[startIndex];

        this.ctx.beginPath();

        this.ctx.moveTo(pt0.x,pt0.y);

        for(var j=startIndex;j<this.lastLength;j++){

            var pt=this.workingStrokes[j];

            this.ctx.lineTo(pt.x,pt.y);

        }

        this.ctx.stroke();

    };

    // Set up brush to listen to events
    var brush = new PrimitiveBrush(canvas.getContext('2d'));

    canvas.addEventListener('mousedown', brush.start.bind(brush));
    canvas.addEventListener('mousemove', brush.move.bind(brush));
    canvas.addEventListener('mouseup', brush.end.bind(brush));

}); // end $(function(){});
</script>
</head>
<body>
    <canvas id="drawing" width=300 height=300></canvas>
</body>
</html>

也只是一个性能点:调用makePoint会降低性能。

  • 缓存canvas.offsetLeft / canvas.offsetTop
  • 在所需的相同功能中创建点对象

答案 1 :(得分:1)

以下两种方法可以实现更快的线条绘制:

方法1:(推荐)

  • 创建一个放置在主
  • 之上的新画布
  • 当您绘制到顶部画布时,
  • 释放鼠标时,将绘制的点转移到主画布并存储点,并清除顶部画布

如果您需要或希望能够绘制很长的线条,您可以随时分开并进行传输以减少重绘的点数。

方法2:

当你绘制线条末端时,质量稍低一些,但它会在鼠标向上固定:

  • 在鼠标按下时存​​储鼠标的最后位置
  • 在鼠标移动中,在最后一个点和当前点之间画一条线
  • 绘制每一行后更新当前点
  • 鼠标向上重绘以使线条连续

原理的一个非常简短的例子(伪代码):

var lastX, lastY, isDown = false, ...

canvas.onmousedown = function(e) {
    var pos = getXY(e); // get mouse position somehow
    isDown = true;

    lastX = pos.x;
    lastY = pos.y:
]

canvas.onmousemove = function(e) {

    if (!isDown) return;

    var pos = getXY(e); // get mouse position somehow

    drawLine(lastX, lastY, pos.x, pos.y);

    points.push(pos);   // store point somewhere

    lastX = pos.x;
    lastY = pos.y:
]

您可以立即修复当前代码中的另一件事是在beginPath()方法中插入_draw()。只是这样做会提高速度,因为现在发生的是你将每个点累积到当前路径,并且对于每个新点,你绘制所有前一行的所有路径向量,并重绘以及重绘的点。循环(这意味着每个新点都有重绘的指数量。)

PrimitiveBrush.prototype._draw = function () {
    this.ctx.clearRect(0, 0, this.ctx.width, this.ctx.height)

    this.ctx.beginPath();  // insert here

    for (var i = 0, l = this.strokes.length; i < l; i++) {
        this.ctx.moveTo(this.strokes[i][0].x, this.strokes[i][0].y);
        for (var j = 1, m = this.strokes[i].length; j < m; j++) {
            var point = this.strokes[i][j];
            this.ctx.lineTo(point.x, point.y);
        }
    }

    this.ctx.stroke();
};

但它不会解决主要问题 - 考虑使用上述两种方法中的一种。

希望这有帮助。