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

时间:2013-09-08 15:00:37

标签: javascript jquery canvas html5-canvas

following code 使用jQuery在HTML 5 Canvas中创建一个圆圈:

代码:

//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();
}

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

examples

我已经研究过并发现了 article ,但无法应用它。

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

有更好的方法吗?我感觉会涉及很多数学:)

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

5 个答案:

答案 0 :(得分:12)

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

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

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

Snapshot

您可以像往常一样设置strokeStylelineWidth等来更改颜色,厚度等。

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

handDrawCircle(context, x, y, radius [, rounds] [, callback]);

(提供callback,因为动画使函数异步)。

代码分为两部分:

  1. 生成积分
  2. 动画积分
  3. <强>初始化

    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
    

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

    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);
    }
    

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

    速度由上一步中的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();
            }
        }
    }
    

    提示:要获得更逼真的笔画,您可以将globalAlpha缩小为例如0.7

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

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

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

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

    rotate = Math.random() * 0.5;
    
    ctx.save();
    ctx.translate(cx, cy);
    ctx.rotate(-rotate);
    ctx.translate(-cx, -cy);
    

    当循环结束时:

    if (i < points.length) {
        requestAnimationFrame(draw);
    } else {
       ctx.restore();
    }
    

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

    圆圈看起来更像是这样:

    Snapshot tilted

    <强>更新

    要解决上面提到的问题(评论字段太小:-)):制作动画线条实际上要复杂一些,特别是在这样的情况下,你可以进行圆周运动以及随机边界。

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

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

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

    handDrawCircle(context, x, y, radius [, rounds] [, callback]);
    

    然后动画结束时:

    if (i < points.length) {
        requestAnimationFrame(draw);
    
    } else {
        ctx.restore();
        if (typeof callback === 'function')
            callback();  /// call next function
    }
    

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

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

    当我们使用clearRect(这将使线条平滑而不是“锯齿”,因为我们之间不使用清晰但只是在顶部绘制)我们需要考虑实现顶部画布到用动画完成动画,当动画结束时我们将结果绘制到主画布。

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

答案 1 :(得分: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) );
}

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

有很多不同的方法可以做到这一点。我选择trig函数来简化模拟,因为速度不是这里的一个因素。

更新

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

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

显然,圆的中心位于(200,200),因为用三角函数绘制圆(相当于具有垂直半径RY和水平半径RX的省略号)的公式是

x = centerX + RX * cos ( angle )
y = centerY + RY * sin ( angle )

答案 2 :(得分:3)

您的任务似乎有3个要求:

  1. 手绘形状。
  2. “有机”而不是“超精确”的中风。
  3. 逐步显示圆圈,而不是一次性显示圆圈。
  4. 要开始使用,请查看Andrew Trice的这个漂亮的目标演示。

    这个惊人的圈子是我手绘的(你现在可以笑了......!)

    My amazing circle created with Andrew's technique

    Andrew的演示执行了您的要求的第1步和第2步。

    它可以让您使用有机外观的“画笔效果”来绘制圆形(或任何形状),而不是通常在画布中使用的通常的超精确线条。

    通过在手绘点之间重复绘制画笔图像来实现“画笔效果”

    以下是演示:

    http://tricedesigns.com/portfolio/sketch/brush.html#

    代码可以在GitHub上找到:

    https://github.com/triceam/HTML5-Canvas-Brush-Sketch

    安德鲁·特里斯(Andrew Trice)的演示抽取并忘记构成你圈子的线条。

    你的任务是恭维你的第三个要求(记住笔画):

    • 手绘一个自己的圆圈,
    • 将构成圆圈的每个线段保存在一个数组中
    • 使用安德鲁的程式化画笔技术“播放”这些片段。

    结果:一个手绘和程式化的圆圈,逐渐显示而不是一次性显示。

    你有一个有趣的项目...如果你感到慷慨,请分享你的结果!

答案 3 :(得分:2)

面对类似的任务,我为SVG或HTML5 Canvas创建了一个卡通风格的JS绘图库。它适用于Raphael.jsD3.jsSVG.js的插件或Canvas的lib。它被称为comic.js,可以在github找到。在其他形状中,它可以绘制您要求的类似圆圈。它基于您提到的article

这可以产生:

comic.js screenshot

答案 4 :(得分:1)

See live demo here.也可以gist

<div id="container">
    <svg width="100%" height="100%" viewBox='-1.5 -1.5 3 3'></svg>
</div>

#container {
  width:500px;
  height:300px;
}
path.ln {
  stroke-width: 3px;
  stroke: #666;
  fill: none;
  vector-effect: non-scaling-stroke;
  stroke-dasharray: 1000;
  stroke-dashoffset: 1000;
  -webkit-animation: dash 5s ease-in forwards;
  -moz-animation:dash 5s ease-in forwards;
  -o-animation:dash 5s ease-in forwards;
  animation:dash 5s ease-in forwards;
}

@keyframes dash {
  to { stroke-dashoffset: 0; }
}

function path(δr_min,δr_max, el0_min, el0_max, δel_min,δel_max) {

    var c = 0.551915024494;
    var atan = Math.atan(c)
    var d = Math.sqrt( c * c + 1 * 1 ), r = 1;
    var el = (el0_min + Math.random() * (el0_max - el0_min)) * Math.PI / 180;
    var path = 'M';

    path += [r * Math.sin(el), r * Math.cos(el)];
    path += ' C' + [d * r * Math.sin(el + atan), d * r * Math.cos(el + atan)];

    for (var i = 0; i < 4; i++) {
        el += Math.PI / 2 * (1 + δel_min + Math.random() * (δel_max - δel_min));
        r *= (1 + δr_min + Math.random()*(δr_max - δr_min));
        path += ' ' + (i?'S':'') + [d * r * Math.sin(el - atan), d * r * Math.cos(el - atan)];
        path += ' ' + [r * Math.sin(el), r * Math.cos(el)];
    }

    return path;
}

function cX(λ_min, λ_max, el_min, el_max) {
    var el = (el_min + Math.random()*(el_max - el_min));
    return 'rotate(' + el + ') ' + 'scale(1, ' + (λ_min + Math.random()*(λ_max - λ_min)) + ')'+ 'rotate(' + (-el) + ')';
}

function canvasArea() {
    var width = Math.floor((Math.random() * 500) + 450);
  var height = Math.floor((Math.random() * 300) + 250);
    $('#container').width(width).height(height);
}
d3.selectAll( 'svg' ).append( 'path' ).classed( 'ln', true) .attr( 'd', path(-0.1,0, 0,360, 0,0.2 )).attr( 'transform', cX( 0.6, 0.8, 0, 360 ));

setTimeout(function() { location = '' } ,5000)