HTML 5画布中的一手拉圆模拟

use*_*388 15 javascript jquery canvas html5-canvas

下面的代码创建使用jQuery在HTML 5画布的圆:

码:

//get a reference to the canvas
var ctx = $('#canvas')[0].getContext("2d");

DrawCircle(75, 75, 20);

//draw a circle
function DrawCircle(x, y, radius)
{
    ctx.beginPath();
    ctx.arc(x, y, radius, 0, Math.PI*2, true); 
    ctx.fillStyle = 'transparent';
    ctx.lineWidth = 2;
    ctx.strokeStyle = '#003300';
    ctx.stroke();
    ctx.closePath();
    ctx.fill();
}
Run Code Online (Sandbox Code Playgroud)

我正在尝试模拟以下任何类型的圈子:

例子

我已经研究并发现了这篇文章,但无法应用它.

我希望绘制圆而不仅仅是出现.

有一个更好的方法吗?我感觉会有很多数学问题:)

PS我喜欢PaperJs的简单性,也许这是使用简化路径的最简单方法?

小智 14

这里已经有了很好的解决方案.我想添加已经呈现的内容的变体 - 如果想要模拟手绘圆圈,除了某些三角函数之外没有太多选项.

我首先建议实际记录一个真正的手绘圆圈.您可以随时记录点以及timeStamp重现精确的绘图.您可以将其与线平滑算法结合使用.

这里的解决方案产生如下的圆圈:

快照

您可以通过设置改变颜色,厚度等strokeStyle,lineWidth等一切正常.

要绘制一个圆圈,只需致电:

handDrawCircle(context, x, y, radius [, rounds] [, callback]);
Run Code Online (Sandbox Code Playgroud)

(callback提供动画使函数异步).

代码分为两部分:

  1. 生成积分
  2. 为点动画

初始化:

function handDrawCircle(ctx, cx, cy, r, rounds, callback) {

    /// rounds is optional, defaults to 3 rounds    
    rounds = rounds ? rounds : 3;

    var x, y,                                      /// the calced point
        tol = Math.random() * (r * 0.03) + (r * 0.025), ///tolerance / fluctation
        dx = Math.random() * tol * 0.75,           /// "bouncer" values
        dy = Math.random() * tol * 0.75,
        ix = (Math.random() - 1) * (r * 0.0044),   /// speed /incremental
        iy = (Math.random() - 1) * (r * 0.0033),
        rx = r + Math.random() * tol,              /// radius X 
        ry = (r + Math.random() * tol) * 0.8,      /// radius Y
        a = 0,                                     /// angle
        ad = 3,                                    /// angle delta (resolution)
        i = 0,                                     /// counter
        start = Math.random() + 50,                /// random delta start
        tot = 360 * rounds + Math.random() * 50 - 100,  /// end angle
        points = [],                               /// the points array
        deg2rad = Math.PI / 180;                   /// degrees to radians
Run Code Online (Sandbox Code Playgroud)

在主循环中,我们不会随机弹跳,而是随机增量,然后用该值线性增加,如果我们处于边界(公差),则反转它.

for (; i < tot; i += ad) {
    dx += ix;
    dy += iy;

    if (dx < -tol || dx > tol) ix = -ix;
    if (dy < -tol || dy > tol) iy = -iy;

    x = cx + (rx + dx * 2) * Math.cos(i * deg2rad + start);
    y = cy + (ry + dy * 2) * Math.sin(i * deg2rad + start);

    points.push(x, y);
}
Run Code Online (Sandbox Code Playgroud)

在最后一段我们只是渲染我们的点数.

速度由da上一步骤中的(增量角度)确定:

    i = 2;

    /// start line    
    ctx.beginPath();
    ctx.moveTo(points[0], points[1]);

    /// call loop
    draw();

    function draw() {

        ctx.lineTo(points[i], points[i + 1]);
        ctx.stroke();

        ctx.beginPath();
        ctx.moveTo(points[i], points[i + 1]);

        i += 2;

        if (i < points.length) {
            requestAnimationFrame(draw);
        } else {
            if (typeof callback === 'function')
                callback();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

提示:为了获得更逼真的笔画,您可以减少globalAlpha到例如0.7.

但是,为了使其正常工作,您需要首先在屏幕外画布上绘制实体,然后将屏幕外画布快速插入globalAlpha到每个帧的主画布(具有该集合),否则笔划将在每个点之间重叠(其中看起来不太好.

对于正方形,您可以使用与圆相同的方法,但不是使用半径和角度,而是将变量应用于线.抵消增量以使线不直.

我稍微调整了一些值,但可以随意调整它们以获得更好的结果.

要使圆圈"倾斜"一点,您可以先将画布稍微旋转一下:

rotate = Math.random() * 0.5;

ctx.save();
ctx.translate(cx, cy);
ctx.rotate(-rotate);
ctx.translate(-cx, -cy);
Run Code Online (Sandbox Code Playgroud)

当循环结束时:

if (i < points.length) {
    requestAnimationFrame(draw);
} else {
   ctx.restore();
}
Run Code Online (Sandbox Code Playgroud)

(包含在上面链接的演示中).

圆圈看起来更像是这样的:

快照倾斜

更新

为了解决上面提到的问题(评论字段太小:-)):做动画线实际上有点复杂,特别是在这样的情况下,你可以进行圆周运动以及随机边界.

参考.注释点1:公差与半径密切相关,因为它定义了最大波动.我们可以修改代码以采用容差(并且ix/iy根据半径定义它将如何"快速"变化).这就是我的意思,通过调整,找到适用于所有尺寸的价值/甜点.圆越小,变化越小.(可选)将这些值指定为函数的参数.

第2点:因为我们正在为圆圈设置动画,所以函数变得异步.如果我们一个接一个地绘制两个圆圈,它们会弄乱画布,因为新的点被添加到两个圆圈的路径中,然后被纵横交错.

我们可以通过提供回调机制来解决这个问题:

handDrawCircle(context, x, y, radius [, rounds] [, callback]);
Run Code Online (Sandbox Code Playgroud)

然后当动画结束时:

if (i < points.length) {
    requestAnimationFrame(draw);

} else {
    ctx.restore();
    if (typeof callback === 'function')
        callback();  /// call next function
}
Run Code Online (Sandbox Code Playgroud)

代码将按原样遇到的另一个问题(请记住代码是一个示例,而不是完整的解决方案:-))是粗线:

当我们逐段绘制时,画布不知道如何计算相对于前一段的线的对接角度.这是路径概念的一部分.当您使用多个线段描边路径时,画布会知道对接线(线的末端)的角度.所以在这里我们要么从开始到当前点画线,要么在两者之间做一个明确的或只是一个小的lineWidth值.

当我们使用clearRect(这将使线条平滑而不是"锯齿",因为我们不使用中间的清晰但只是在顶部绘制)我们需要考虑实现顶部画布来执行动画,并在动画完成时我们将结果绘制到主画布上.

现在我们开始看到涉及"复杂性"的一部分.这当然是因为画布是"低级"的,因为我们需要为所有东西提供所有逻辑.我们基本上每次使用画布做更多事情时都会构建系统而不仅仅是绘制简单的形状和图像(但这也提供了很大的灵活性).


Ale*_*kka 8

以下是我为此答案创建的一些基础知识:

http://jsfiddle.net/Exceeder/TPDmn/

基本上,当你画一个圆圈时,你需要考虑手部缺陷.因此,在以下代码中:

var img = new Image();
img.src="data:image/png;base64,...";

var ctx = $('#sketch')[0].getContext('2d');
function draw(x,y) {
  ctx.drawImage(img, x, y);
}

for (var i=0; i<500; i++) {
    var radiusError = +10 - i/20;
    var d = 2*Math.PI/360 * i;
    draw(200 + 100*Math.cos(d), 200 + (radiusError+80)*Math.sin(d) );
}
Run Code Online (Sandbox Code Playgroud)

注意当角度(和位置)增长时垂直半径误差如何变化.欢迎你玩这个小提琴,直到你感受到"感觉"什么组件做什么.例如,将另一个组件引入radiusError是有意义的,该组件通过慢慢改变它的随机量来模拟"不稳定"的手.

有很多不同的方法可以做到这一点.我选择触发功能以简化模拟,因为速度不是这里的一个因素.

更新:

例如,这会使它不那么完美:

var d = 2*Math.PI/360 * i;
var radiusError = +10 - i/20 + 10*Math.sin(d);
Run Code Online (Sandbox Code Playgroud)

显然,该圆的中心位于(200,200),作为公式画一个圆圈(而,省略与垂直半径RY和水平半径RX)与三角函数是

x = centerX + RX * cos ( angle )
y = centerY + RY * sin ( angle )
Run Code Online (Sandbox Code Playgroud)

  • @ user1477388我创建了一个动画版本(可能不是最干净的版本):http://jsfiddle.net/TPDmn/2/ (2认同)