src/lib/curves.ts 2.6 K raw
1
import type { CurvePoint } from "./types";
2
3
export function interpolateSpline(points: CurvePoint[]): Uint8Array {
4
  const lut = new Uint8Array(256);
5
6
  if (points.length < 2) {
7
    for (let i = 0; i < 256; i++) lut[i] = i;
8
    return lut;
9
  }
10
11
  const sorted = [...points].sort((a, b) => a.x - b.x);
12
  const n = sorted.length;
13
14
  if (n === 2) {
15
    const [p0, p1] = sorted;
16
    for (let i = 0; i < 256; i++) {
17
      if (i <= p0.x) {
18
        lut[i] = clamp(p0.y);
19
      } else if (i >= p1.x) {
20
        lut[i] = clamp(p1.y);
21
      } else {
22
        const t = (i - p0.x) / (p1.x - p0.x);
23
        lut[i] = clamp(p0.y + t * (p1.y - p0.y));
24
      }
25
    }
26
    return lut;
27
  }
28
29
  // Monotone cubic Hermite interpolation (Fritsch-Carlson)
30
  const xs = sorted.map((p) => p.x);
31
  const ys = sorted.map((p) => p.y);
32
  const dx: number[] = [];
33
  const dy: number[] = [];
34
  const m: number[] = [];
35
  const ms: number[] = [];
36
37
  for (let i = 0; i < n - 1; i++) {
38
    dx[i] = xs[i + 1] - xs[i];
39
    dy[i] = ys[i + 1] - ys[i];
40
    ms[i] = dx[i] === 0 ? 0 : dy[i] / dx[i];
41
  }
42
43
  m[0] = ms[0];
44
  for (let i = 1; i < n - 1; i++) {
45
    if (ms[i - 1] * ms[i] <= 0) {
46
      m[i] = 0;
47
    } else {
48
      m[i] = (ms[i - 1] + ms[i]) / 2;
49
    }
50
  }
51
  m[n - 1] = ms[n - 2];
52
53
  // Fritsch-Carlson monotonicity
54
  for (let i = 0; i < n - 1; i++) {
55
    if (ms[i] === 0) {
56
      m[i] = 0;
57
      m[i + 1] = 0;
58
    } else {
59
      const alpha = m[i] / ms[i];
60
      const beta = m[i + 1] / ms[i];
61
      const tau = alpha * alpha + beta * beta;
62
      if (tau > 9) {
63
        const s = 3 / Math.sqrt(tau);
64
        m[i] = s * alpha * ms[i];
65
        m[i + 1] = s * beta * ms[i];
66
      }
67
    }
68
  }
69
70
  for (let x = 0; x < 256; x++) {
71
    if (x <= xs[0]) {
72
      lut[x] = clamp(ys[0]);
73
      continue;
74
    }
75
    if (x >= xs[n - 1]) {
76
      lut[x] = clamp(ys[n - 1]);
77
      continue;
78
    }
79
80
    let seg = 0;
81
    for (let i = 0; i < n - 1; i++) {
82
      if (x >= xs[i] && x < xs[i + 1]) {
83
        seg = i;
84
        break;
85
      }
86
    }
87
88
    const h = dx[seg];
89
    const t = (x - xs[seg]) / h;
90
    const t2 = t * t;
91
    const t3 = t2 * t;
92
93
    const h00 = 2 * t3 - 3 * t2 + 1;
94
    const h10 = t3 - 2 * t2 + t;
95
    const h01 = -2 * t3 + 3 * t2;
96
    const h11 = t3 - t2;
97
98
    const val = h00 * ys[seg] + h10 * h * m[seg] + h01 * ys[seg + 1] + h11 * h * m[seg + 1];
99
    lut[x] = clamp(val);
100
  }
101
102
  return lut;
103
}
104
105
function clamp(v: number): number {
106
  return Math.max(0, Math.min(255, Math.round(v)));
107
}
108
109
export function isIdentityCurve(points: CurvePoint[]): boolean {
110
  if (points.length !== 2) return false;
111
  return points[0].x === 0 && points[0].y === 0 && points[1].x === 255 && points[1].y === 255;
112
}