我希望通过向上或向下移动像素列来修改图像,使每列偏移跟随曲线。
我希望曲线以一些平滑的方式与6个左右的点相交。我想象一下,在图像x坐标上循环并调用一个曲线函数,该函数返回该偏移处曲线的y坐标,从而告诉我移动每列像素的数量。
我已经研究了各种类型的曲线,但坦率地说,我有点失落,我希望有一个现成的解决方案,可以让我插入我的点数据并吐出我需要的数据。我并不太习惯使用什么样的曲线,只要它看起来“光滑”。
任何人都可以帮我吗?
我正在使用HTML5和canvas。给出的答案here看起来像我追求的那种东西,但它指的是一个R库(我猜),这对我来说是希腊语!
答案 0 :(得分:0)
一个非常简单的解决方案,如果你只想在y方向上的曲线是使用sigmoid曲线来插入控制点之间的y pos
// where 0 <= x <= 1 and p > 1
// return value between 0 and 1 inclusive.
// p is power and determines the amount of curve
function sigmoidCurve(x, p){
x = x < 0 ? 0 : x > 1 ? 1 : x;
var xx = Math.pow(x, p);
return xx / (xx + Math.pow(1 - x, p))
}
如果您希望x坐标px
处的y位置位于两个控制点x1
,y1
和x2
之间,y2
首先在px
,x1
x2
的标准化位置
var nx = (px - x1) / (x2 - x1); // normalised dist between points
将值插入sigmoidCurve
var c = sigmoidCurve(nx, 2); // curve power 2
使用该值来计算y
var py = (y2 - y1) * c + y1;
你在点之间的曲线上有一个点。
作为单个表达式
var py = (y2 - y1) *sigmoidCurve((px - x1) / (x2 - x1), 2) + y1;
如果将sigmoid曲线的功率设置为1.5,那么它几乎是一个三次贝塞尔曲线的完美匹配
此示例显示动画曲线。函数getPointOnCurve将获得位置x
处曲线上任意点的y pos
const ctx = canvas.getContext("2d");
const curve = [[10, 0], [120, 100], [200, 50], [290, 150]];
const pos = {};
function cubicInterpolation(x, p0, p1, p2, p3){
x = x < 0 ? 0 : x > 1 ? 1 : x;
return p1 + 0.5*x*(p2 - p0 + x*(2*p0 - 5*p1 + 4*p2 - p3 + x*(3*(p1 - p2) + p3 - p0)));
}
function sigmoidCurve(x, p){
x = x < 0 ? 0 : x > 1 ? 1 : x;
var xx = Math.pow(x, p);
return xx / (xx + Math.pow(1 - x, p))
}
// functional for loop
const doFor = (count, cb) => { var i = 0; while (i < count && cb(i++) !== true); };
// functional iterator
const eachOf = (array, cb) => { var i = 0; const len = array.length; while (i < len && cb(array[i], i++, len) !== true ); };
// find index for x in curve
// returns pos{ index, y }
// if x is at a control point then return the y value and index set to -1
// if not at control point the index is of the point befor x
function getPosOnCurve(x,curve, pos = {}){
var len = curve.length;
var i;
pos.index = -1;
pos.y = null;
if(x <= curve[0][0]) { return (pos.y = curve[0][1], pos) }
if(x >= curve[len - 1][0]) { return (pos.y = curve[len - 1][1], pos) }
i = 0;
var found = false;
while(!found){ // some JS optimisers will mark "Do not optimise"
// code that do not have an exit clause.
if(curve[i++][0] <x && curve[i][0] >= x) { break }
}
i -= 1;
if(x === curve[i][0]) { return (pos.y = curve[i][1], pos) }
pos.index =i
return pos;
}
// Using Cubic interpolation to create the curve
function getPointOnCubicCurve(x, curve, power){
getPosOnCurve(x, curve, pos);
if(pos.index === -1) { return pos.y };
var i = pos.index;
// get interpolation values for points around x
var p0,p1,p2,p3;
p1 = curve[i][1];
p2 = curve[i+1][1];
p0 = i === 0 ? p1 : curve[i-1][1];
p3 = i === curve.length - 2 ? p2 : curve[i+2][1];
// get unit distance of x between curve i, i+1
var ux = (x - curve[i][0]) / (curve[i + 1][0] - curve[i][0]);
return cubicInterpolation(ux, p0, p1, p2, p3);
}
// Using Sigmoid function to get curve.
// power changes curve power = 1 is line power > 1 tangents become longer
// With the power set to 1.5 this is almost a perfect match for
// cubic bezier solution.
function getPointOnCurve(x, curve, power){
getPosOnCurve(x, curve, pos);
if(pos.index === -1) { return pos.y };
var i = pos.index;
var p = sigmoidCurve((x - curve[i][0]) / (curve[i + 1][0] - curve[i][0]) ,power);
return curve[i][1] + (curve[i + 1][1] - curve[i][1]) * p;
}
const step = 2;
var w = canvas.width;
var h = canvas.height;
var cw = w / 2; // center width and height
var ch = h / 2;
function update(timer){
ctx.setTransform(1,0,0,1,0,0); // reset transform
ctx.globalAlpha = 1; // reset alpha
ctx.clearRect(0,0,w,h);
eachOf(curve, (point) => {
point[1] = Math.sin(timer / (((point[0] + 10) % 71) * 100) ) * ch * 0.8 + ch;
});
ctx.strokeStyle = "black";
ctx.beginPath();
doFor(w / step, x => { ctx.lineTo(x * step, getPointOnCurve(x * step, curve, 1.5) - 10)});
ctx.stroke();
ctx.strokeStyle = "blue";
ctx.beginPath();
doFor(w / step, x => { ctx.lineTo(x * step, getPointOnCubicCurve(x * step, curve, 1.5) + 10)});
ctx.stroke();
ctx.strokeStyle = "black";
eachOf(curve,point => ctx.strokeRect(point[0] - 2,point[1] - 2 - 10, 4, 4) );
eachOf(curve,point => ctx.strokeRect(point[0] - 2,point[1] - 2 + 10, 4, 4) );
requestAnimationFrame(update);
}
requestAnimationFrame(update);
&#13;
canvas { border : 2px solid black; }
&#13;
<canvas id="canvas"></canvas>
&#13;
<强>更新强>
我在上面的演示中添加了第二种曲线类型,因为蓝色曲线偏离了原始的S形曲线黑色。
上述功能可适用于各种插值方法。我添加了函数
function cubicInterpolation(x, p0, p1, p2, p3){
x = x < 0 ? 0 : x > 1 ? 1 : x;
return p1 + 0.5*x*(p2 - p0 + x*(2*p0 - 5*p1 + 4*p2 - p3 + x*(3*(p1 - p2) + p3 - p0)));
}
根据x两侧两点的线斜率生成曲线。此方法适用于均匀间隔的点,但如果间距不均匀(例如此示例),则仍然有效。如果间距变得太不均匀,您可以注意到该点处的曲线有点扭结。
此外,拍摄过程中的曲线可能是一个问题。
的更多信息