沿曲线的梯度冲程在帆布

时间:2014-06-04 00:22:04

标签: html5 canvas gradient curve bezier

我试图在画布中绘制一条曲线,沿着曲线绘制线性渐变线条样式,如this image中所示。在该页面上有一个链接的svg文件,它提供了有关如何在svg中完成效果的说明。也许在画布中可以采用类似的方法吗?

2 个答案:

答案 0 :(得分:31)

演示:http://jsfiddle.net/m1erickson/4fX5D/

创建一个沿路径改变的渐变

相当容易

enter image description here

创建一个在路径中改变的渐变

更加困难

enter image description here

要在路径上创建渐变,您可以绘制许多与路径相切的渐变线:

enter image description here

如果绘制足够的切线,则眼睛会将曲线视为路径上的渐变。

enter image description here

注意:Jaggies可能发生在路径渐变的外部。那是因为渐变实际上是由数百条切线组成的。但是你可以通过使用适当的颜色在渐变的任一侧画一条线来平滑锯齿(这里的反锯齿线在顶部是红色,在底部是紫色)。

以下是在路径中创建渐变的步骤:

  • 在路径上绘制数百个点。

  • 计算这些点的路径角度。

  • 在每个点处,创建一个线性渐变,并在该点的切线上绘制一条渐变描边线。是的,您必须为每个点创建一个新渐变,因为线性渐变必须与该点相切的线的角度匹配。

  • 为了减少绘制多条单独线条所造成的锯齿效果,您可以沿着渐变路径的顶部和底部绘制一条平滑的路径,以覆盖锯齿状。

以下是带注释的代码:

<!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(){

    // canvas related variables
    var canvas=document.getElementById("canvas");
    var ctx=canvas.getContext("2d");

    // variables defining a cubic bezier curve
    var PI2=Math.PI*2;
    var s={x:20,y:30};
    var c1={x:200,y:40};
    var c2={x:40,y:200};
    var e={x:270,y:220};

    // an array of points plotted along the bezier curve
    var points=[];

    // we use PI often so put it in a variable
    var PI=Math.PI;

    // plot 400 points along the curve
    // and also calculate the angle of the curve at that point
    for(var t=0;t<=100;t+=0.25){

        var T=t/100;

        // plot a point on the curve
        var pos=getCubicBezierXYatT(s,c1,c2,e,T);

        // calculate the tangent angle of the curve at that point
        var tx = bezierTangent(s.x,c1.x,c2.x,e.x,T);
        var ty = bezierTangent(s.y,c1.y,c2.y,e.y,T);
        var a = Math.atan2(ty, tx)-PI/2;

        // save the x/y position of the point and the tangent angle
        // in the points array
        points.push({
            x:pos.x,
            y:pos.y,
            angle:a
        });

    }


    // Note: increase the lineWidth if 
    // the gradient has noticable gaps 
    ctx.lineWidth=2;

    // draw a gradient-stroked line tangent to each point on the curve
    for(var i=0;i<points.length;i++){

        // calc the topside and bottomside points of the tangent line
        var offX1=points[i].x+20*Math.cos(points[i].angle);
        var offY1=points[i].y+20*Math.sin(points[i].angle);
        var offX2=points[i].x+20*Math.cos(points[i].angle-PI);
        var offY2=points[i].y+20*Math.sin(points[i].angle-PI);

        // create a gradient stretching between 
        // the calculated top & bottom points
        var gradient=ctx.createLinearGradient(offX1,offY1,offX2,offY2);
        gradient.addColorStop(0.00, 'red'); 
        gradient.addColorStop(1/6, 'orange'); 
        gradient.addColorStop(2/6, 'yellow'); 
        gradient.addColorStop(3/6, 'green') 
        gradient.addColorStop(4/6, 'aqua'); 
        gradient.addColorStop(5/6, 'blue'); 
        gradient.addColorStop(1.00, 'purple'); 

        // draw the gradient-stroked line at this point
        ctx.strokeStyle=gradient;
        ctx.beginPath();
        ctx.moveTo(offX1,offY1);
        ctx.lineTo(offX2,offY2);
        ctx.stroke();
    }


    // draw a top stroke to cover jaggies
    // on the top of the gradient curve
    var offX1=points[0].x+20*Math.cos(points[0].angle);
    var offY1=points[0].y+20*Math.sin(points[0].angle);
    ctx.strokeStyle="red";
    // Note: increase the lineWidth if this outside of the
    //       gradient still has jaggies
    ctx.lineWidth=1.5;
    ctx.beginPath();
    ctx.moveTo(offX1,offY1);
    for(var i=1;i<points.length;i++){
        var offX1=points[i].x+20*Math.cos(points[i].angle);
        var offY1=points[i].y+20*Math.sin(points[i].angle);
        ctx.lineTo(offX1,offY1);
    }
    ctx.stroke();


    // draw a bottom stroke to cover jaggies
    // on the bottom of the gradient
    var offX2=points[0].x+20*Math.cos(points[0].angle+PI);
    var offY2=points[0].y+20*Math.sin(points[0].angle+PI);
    ctx.strokeStyle="purple";
    // Note: increase the lineWidth if this outside of the
    //       gradient still has jaggies
    ctx.lineWidth=1.5;
    ctx.beginPath();
    ctx.moveTo(offX2,offY2);
    for(var i=0;i<points.length;i++){
        var offX2=points[i].x+20*Math.cos(points[i].angle+PI);
        var offY2=points[i].y+20*Math.sin(points[i].angle+PI);
        ctx.lineTo(offX2,offY2);
    }
    ctx.stroke();


    //////////////////////////////////////////
    // helper functions
    //////////////////////////////////////////

    // calculate one XY point along Cubic Bezier at interval T
    // (where T==0.00 at the start of the curve and T==1.00 at the end)
    function getCubicBezierXYatT(startPt,controlPt1,controlPt2,endPt,T){
        var x=CubicN(T,startPt.x,controlPt1.x,controlPt2.x,endPt.x);
        var y=CubicN(T,startPt.y,controlPt1.y,controlPt2.y,endPt.y);
        return({x:x,y:y});
    }

    // cubic helper formula at T distance
    function CubicN(T, a,b,c,d) {
        var t2 = T * T;
        var t3 = t2 * T;
        return a + (-a * 3 + T * (3 * a - a * T)) * T
        + (3 * b + T * (-6 * b + b * 3 * T)) * T
        + (c * 3 - c * 3 * T) * t2
        + d * t3;
    }

    // calculate the tangent angle at interval T on the curve
    function bezierTangent(a, b, c, d, t) {
        return (3 * t * t * (-a + 3 * b - 3 * c + d) + 6 * t * (a - 2 * b + c) + 3 * (-a + b));
    };

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

答案 1 :(得分:0)

我正在做类似的事情,我只想添加几件事。 markE的答案很好,但他所谓的曲线切线实际上是法线或垂直于曲线的线。 (切线是平行的,法线是垂直的)

对于我的特定应用程序,我在具有透明度的线上使用渐变。在这种情况下,重要的是要接近像素完美的渐变区域,因为重叠的透明度将被绘制两次,从而更改所需的颜色。因此,我没有画一束垂直于曲线的线,而是将曲线划分为四边形,并对每条线应用了线性渐变。此外,使用这些四边形区域可以减少必须进行绘制的调用次数,这可以使其效率更高。您不需要大量的区域即可获得相当平滑的效果,并且使用的区域越少,渲染速度就越快。

我改编了markE的代码,因此,他的出色回答值得称赞。这是小提琴:https://jsfiddle.net/hvyt58dz/

这是我使用的改编代码:

// canvas related variables
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");

// variables defining a cubic bezier curve
var PI2 = Math.PI * 2;
var s = {
    x: 20,
    y: 30
};
var c1 = {
    x: 200,
    y: 40
};
var c2 = {
    x: 40,
    y: 200
};
var e = {
    x: 270,
    y: 220
};

// an array of points plotted along the bezier curve
var points = [];

// we use PI often so put it in a variable
var PI = Math.PI;

// plot 400 points along the curve
// and also calculate the angle of the curve at that point
var step_size = 100/18;
for (var t = 0; t <= 100 + 0.1; t += step_size) {

    var T = t / 100;


    // plot a point on the curve
    var pos = getCubicBezierXYatT(s, c1, c2, e, T);

    // calculate the tangent angle of the curve at that point
    var tx = bezierTangent(s.x, c1.x, c2.x, e.x, T);
    var ty = bezierTangent(s.y, c1.y, c2.y, e.y, T);
    var a = Math.atan2(ty, tx) - PI / 2;

    // save the x/y position of the point and the tangent angle
    // in the points array
    points.push({
        x: pos.x,
        y: pos.y,
        angle: a
    });

}


// Note: increase the lineWidth if 
// the gradient has noticable gaps 
ctx.lineWidth = 2;
var overlap = 0.2;
var outside_color = 'rgba(255,0,0,0.0)';
var inside_color = 'rgba(255,0,0,0.7)';

// draw a gradient-stroked line tangent to each point on the curve
var line_width = 40;
var half_width = line_width/2;
for (var i = 0; i < points.length - 1; i++) {

    var x1 = points[i].x, y1 = points[i].y;
    var x2 = points[i+1].x, y2 = points[i+1].y;
    var angle1 = points[i].angle, angle2 = points[i+1].angle;
    var midangle = (angle1 + angle2)/ 2;
    // calc the topside and bottomside points of the tangent line
    var gradientOffsetX1 = x1 + half_width * Math.cos(midangle);
    var gradientOffsetY1 = y1 + half_width * Math.sin(midangle);
    var gradientOffsetX2 = x1 + half_width * Math.cos(midangle - PI);
    var gradientOffsetY2 = y1 + half_width * Math.sin(midangle - PI); 
    var offX1 = x1 + half_width * Math.cos(angle1);
    var offY1 = y1 + half_width * Math.sin(angle1);
    var offX2 = x1 + half_width * Math.cos(angle1 - PI);
    var offY2 = y1 + half_width * Math.sin(angle1 - PI);

    var offX3 = x2 + half_width * Math.cos(angle2)
                   - overlap * Math.cos(angle2-PI/2);
    var offY3 = y2 + half_width * Math.sin(angle2)
                   - overlap * Math.sin(angle2-PI/2);
    var offX4 = x2 + half_width * Math.cos(angle2 - PI)
                   + overlap * Math.cos(angle2-3*PI/2);
    var offY4 = y2 + half_width * Math.sin(angle2 - PI)
                   + overlap * Math.sin(angle2-3*PI/2);

    // create a gradient stretching between 
    // the calculated top & bottom points
    var gradient = ctx.createLinearGradient(gradientOffsetX1, gradientOffsetY1, gradientOffsetX2, gradientOffsetY2);
    gradient.addColorStop(0.0, outside_color);
    gradient.addColorStop(0.25, inside_color);
    gradient.addColorStop(0.75, inside_color);
    gradient.addColorStop(1.0, outside_color);
    //gradient.addColorStop(1 / 6, 'orange');
    //gradient.addColorStop(2 / 6, 'yellow');
    //gradient.addColorStop(3 / 6, 'green')
    //gradient.addColorStop(4 / 6, 'aqua');
    //gradient.addColorStop(5 / 6, 'blue');
    //gradient.addColorStop(1.00, 'purple');

    // line cap
    if(i == 0){
        var x = x1 - overlap * Math.cos(angle1-PI/2);
        var y = y1 - overlap * Math.sin(angle1-PI/2);
        var cap_gradient = ctx.createRadialGradient(x, y, 0, x, y, half_width);
        ctx.beginPath();
        ctx.arc(x, y, half_width, angle1 - PI, angle1);
        cap_gradient.addColorStop(0.5, inside_color);
        cap_gradient.addColorStop(1.0, outside_color);
        ctx.fillStyle = cap_gradient;
        ctx.fill();
    }
    if(i == points.length - 2){
        var x = x2 + overlap * Math.cos(angle2-PI/2);
        var y = y2 + overlap * Math.sin(angle2-PI/2);
        var cap_gradient = ctx.createRadialGradient(x, y, 0, x, y, half_width);
        ctx.beginPath();
        ctx.arc(x, y, half_width, angle2, angle2 + PI);
        cap_gradient.addColorStop(0.5, inside_color);
        cap_gradient.addColorStop(1.0, outside_color);
        ctx.fillStyle = cap_gradient;
        ctx.fill();
        console.log(x,y);
    }
    // draw the gradient-stroked line at this point
    ctx.fillStyle = gradient;
    ctx.beginPath();
    ctx.moveTo(offX1, offY1);
    ctx.lineTo(offX2, offY2);
    ctx.lineTo(offX4, offY4);
    ctx.lineTo(offX3, offY3);
    ctx.fill();
}

//////////////////////////////////////////
// helper functions
//////////////////////////////////////////

// calculate one XY point along Cubic Bezier at interval T
// (where T==0.00 at the start of the curve and T==1.00 at the end)
function getCubicBezierXYatT(startPt, controlPt1, controlPt2, endPt, T) {
    var x = CubicN(T, startPt.x, controlPt1.x, controlPt2.x, endPt.x);
    var y = CubicN(T, startPt.y, controlPt1.y, controlPt2.y, endPt.y);
    return ({
        x: x,
        y: y
    });
}

// cubic helper formula at T distance
function CubicN(T, a, b, c, d) {
    var t2 = T * T;
    var t3 = t2 * T;
    return a + (-a * 3 + T * (3 * a - a * T)) * T + (3 * b + T * (-6 * b + b * 3 * T)) * T + (c * 3 - c * 3 * T) * t2 + d * t3;
}

// calculate the tangent angle at interval T on the curve
function bezierTangent(a, b, c, d, t) {
    return (3 * t * t * (-a + 3 * b - 3 * c + d) + 6 * t * (a - 2 * b + c) + 3 * (-a + b));
};