|
|
1
Community CDub
4 年前
使用2D canvas API自定义线渲染
没有简单的方法可以在不牺牲大量质量的情况下创建您想要的线条类型。
为了获得最佳质量,需要将线渲染为一组垂直于线并沿线长度方向的小条带。对于每个部分,计算宽度和颜色,然后渲染该条带。
下图将帮助解释我的意思。
问题是2D API在连接单独的渲染路径方面非常糟糕,因此由于每个条带之间的抗锯齿,该方法将产生垂直线图案。
如果将剪辑区域设置为直线,则可以停止此操作。您可以通过绘制线的轮廓并将其设置为剪辑来实现这一点。
然后可以以可接受的质量渲染线
有太多的数学问题无法用一个答案来解释。你需要在贝塞尔曲线上找到点和切线,你需要插值一个梯度,你需要一种定义平滑宽度函数(另一个贝塞尔)的方法,或者在示例中定义一个复杂的抛物线(函数
curve
实例
下面的示例将从单个贝塞尔曲线(第二阶和第三阶)创建您要查找的直线类型。可以使用多条曲线和线段进行调整。
为了获得像素级的消除混叠效果,您必须使用webGL渲染最终路径(但仍需要像示例中那样生成路径)
const ctx = canvas.getContext("2d");
canvas.height = canvas.width = 400;
const geom = (()=>{
const v1 = new Vec();
const v2 = new Vec();
const v3 = new Vec();
const v4 = new Vec();
function Vec(x,y){
this.x = x;
this.y = y;
};
function Bezier(p1,p2,cp1,cp2){
this.p1 = p1;
this.p2 = p2;
this.cp1 = cp1;
this.cp2 = cp2;
}
Bezier.prototype = {
p1 : undefined,
p2 : undefined,
cp1 : undefined,
cp2 : undefined,
vecAt(position,vec){
var c;
if (vec === undefined) { vec = new Vec() }
if (position === 0) {
vec.x = this.p1.x;
vec.y = this.p1.y;
return vec;
}else if (position === 1) {
vec.x = this.p2.x;
vec.y = this.p2.y;
return vec;
}
v1.x = this.p1.x;
v1.y = this.p1.y;
c = position;
if (this.cp2 === undefined) {
v2.x = this.cp1.x;
v2.y = this.cp1.y;
v1.x += (v2.x - v1.x) * c;
v1.y += (v2.y - v1.y) * c;
v2.x += (this.p2.x - v2.x) * c;
v2.y += (this.p2.y - v2.y) * c;
vec.x = v1.x + (v2.x - v1.x) * c;
vec.y = v1.y + (v2.y - v1.y) * c;
return vec;
}
v2.x = this.cp1.x;
v2.y = this.cp1.y;
v3.x = this.cp2.x;
v3.y = this.cp2.y;
v1.x += (v2.x - v1.x) * c;
v1.y += (v2.y - v1.y) * c;
v2.x += (v3.x - v2.x) * c;
v2.y += (v3.y - v2.y) * c;
v3.x += (this.p2.x - v3.x) * c;
v3.y += (this.p2.y - v3.y) * c;
v1.x += (v2.x - v1.x) * c;
v1.y += (v2.y - v1.y) * c;
v2.x += (v3.x - v2.x) * c;
v2.y += (v3.y - v2.y) * c;
vec.x = v1.x + (v2.x - v1.x) * c;
vec.y = v1.y + (v2.y - v1.y) * c;
return vec;
},
tangentAsVec (position, vec ) {
var a, b, c, u;
if (vec === undefined) { vec = new Vec(); }
if (this.cp2 === undefined) {
a = (1-position) * 2;
b = position * 2;
vec.x = a * (this.cp1.x - this.p1.x) + b * (this.p2.x - this.cp1.x);
vec.y = a * (this.cp1.y - this.p1.y) + b * (this.p2.y - this.cp1.y);
}else{
a = (1-position)
b = 6 * a * position;
a *= 3 * a;
c = 3 * position * position;
vec.x = -this.p1.x * a + this.cp1.x * (a - b) + this.cp2.x * (b - c) + this.p2.x * c;
vec.y = -this.p1.y * a + this.cp1.y * (a - b) + this.cp2.y * (b - c) + this.p2.y * c;
}
u = Math.sqrt(vec.x * vec.x + vec.y * vec.y);
vec.x /= u;
vec.y /= u;
return vec;
},
}
return { Vec, Bezier,}
})()
function curve(x,power){
x = 1 - Math.abs(x - 1);
return Math.pow(x,power);
}
function getColFromGrad(pos,grad,isHSL){
var i = 0;
while(i < grad.length -1 && grad[i][0] <= pos && grad[i+1][0] < pos){ i ++ }
var g1 = grad[i];
var g2 = grad[i + 1];
var p = (pos - g1[0]) / (g2[0] - g1[0]);
var r = (g2[1]-g1[1]) * p + g1[1];
var g = (g2[2]-g1[2]) * p + g1[2];
var b = (g2[3]-g1[3]) * p + g1[3];
if(isHSL){ return `hsl(${(r|0)%360},${g|0}%,${b|0}%)` }
return `rgb(${r|0},${g|0},${b|0})`
}
function drawLine(path,width,gradient){
var steps = 300;
var step = 1/steps;
var i = 0;
var pos = V(0,0);
var tangent = V(0,0);
var p = [];
for(i = 0; i <= 1 + step/2; i += step){
path.vecAt(i,pos);
path.tangentAsVec(i,tangent);
var w = curve(i * 2,1/2) * width;
p.push(V(pos.x -tangent.y * w, pos.y + tangent.x * w));
p.push(V(pos.x +tangent.y * w, pos.y - tangent.x * w));
}
ctx.save();
ctx.beginPath();
for(i = 0; i < p.length; i += 2){
ctx.lineTo(p[i].x,p[i].y);
}
for(i = 1; i < p.length; i += 2){
ctx.lineTo(p[p.length - i].x,p[p.length - i].y);
}
ctx.clip();
ctx.lineWidth = 1;
for(i = 0; i < p.length-4; i += 2){
ctx.beginPath();
ctx.strokeStyle = ctx.fillStyle = getColFromGrad(i / (p.length-4),gradient);
ctx.lineTo(p[i].x,p[i].y);
ctx.lineTo(p[i+1].x,p[i+1].y);
ctx.lineTo(p[i+3].x,p[i+3].y);
ctx.lineTo(p[i+2].x,p[i+2].y);
ctx.stroke();
ctx.fill();
}
ctx.restore();
}
var V = (x,y)=> new geom.Vec(x,y);
var b = new geom.Bezier(V(50,50),V(50,390),V(500,10));
var grad = [[0,0,0,0],[0.25,0,255,0],[0.5,255,0,255],[1,255,255,0]];
drawLine(b,10,grad);
var b = new geom.Bezier(V(350,50),V(390,390),V(300,10),V(10,0));
var grad = [[0,255,0,0],[0.25,0,255,0],[0.5,0,255,255],[1,0,0,255]];
drawLine(b,20,grad);
canvas { border : 2px solid black; }
<canvas id="canvas"></canvas>
|