2022.07
Cardinal Spline Algorithm 적용하여 Canvas API 사용한 Drawing 기능 개발
Spline Tool Preview
알고리즘 선택 이유
요구사항에 맞는 Algorithm 중 에서는 (Lagrange, Cardinal) Interpolating 두가지로 좁혀졌다.
초기에는 Lagrange 보간법을 사용했으나, Point 가 좁고 많아질수록 보간값이 불안정하여 곡선이 넓게 튀는 현상이 발견되어, 비교적 Point 가 많아도 안정적인 Cardinal 보간법으로 적용하였다.
Lagrange Interpolating Spline
Cardinal Interpolating Spline
구현
Konva 라이브러리 에서는 커스텀 드로잉 기능을 구현할 수 있게 sceneFunc 라는 함수를 제공한다.
이 함수 내에서 Canvas Context 저수준 객체를 가져와서 직접 알고리즘으로 드로잉하여 Scene 을 생성한다.
사용자가 곡선을 선택하고, 선뿐만이 아닌 영역을 그리기 위해 반복적인 드로잉이 필요하게 되어, 정방향, 역방향 드로잉을 연속적으로 수행하는 한붓그리기 방식으로 영역 생성을 위한 드로잉을 수행하였다.
// **useSpline.ts -** drawSpace
// Spline Space SceneFunc
// calcInterpolatingPoint(X|Y) 함수는 core algorithm 입니다.
function drawSpace(ctx: Context, shape: Shape<ShapeConfig>, hit: boolean) {
let scPx = new Float64Array(n2),
scPy = new Float64Array(n2);
let X, Y;
// Process:
// 1. 첫점에서 너비만큼 X축을 벌려 왼쪽부터 같은 곡선을 그립니다.
// 2. 왼쪽 선에서 선을 이으면서 오른쪽도 같은 곡선을 거꾸로 그리면서 한붓 그리기를 합니다.
// 3. 완성된 선을 지정된 색으로 채웁니다.
if (n > 2) {
for (let dir of [-1, 1]) {
for (let i = 0; i < n; i++) {
X = scPx[i + 1] = Px[i] + dir * infos[i]?.weight;
Y = scPy[i + 1] = Py[i];
}
// [0, last point] not draw, only using by algorithm
initPoint(scPx, scPy)
if (dir === -1) {
ctx.beginPath();
}
if (dir === 1) {
scPx = scPx.reverse();
scPy = scPy.reverse();
}
for (let i = 1; i < n; i++) {
for (let k = 0; k < 26; k++) {
X = calcInterpolatingPointX(scPx, scPy);
Y = calcInterpolatingPointY(scPx, scPy);
ctx.lineTo(X, Y);
}
}
if (dir === 1) {
ctx.closePath();
if (hit) {
// 충돌 영역은 shape 로 그려야 합니다. (화면 표시 안됨, 클릭시 영역 반응)
ctx.fillStrokeShape(shape);
} else {
// gradient 는 shape 로 채우기 불가능하여 raw api 를 사용합니다.
ctx._context.fillStyle = _getGradientSetting(ctx);
ctx._context.fill();
}
}
}
}
}