src/lib/filters.ts 3.1 K raw
1
import type { BasicFilters, CurvesState } from "./types";
2
import { interpolateSpline, isIdentityCurve } from "./curves";
3
4
export function buildCSSFilter(basic: BasicFilters): string {
5
  const parts: string[] = [];
6
7
  // Brightness: slider 0-200, CSS brightness(0-2)
8
  parts.push(`brightness(${basic.brightness / 100})`);
9
10
  // Contrast: slider 0-200, CSS contrast(0-2)
11
  parts.push(`contrast(${basic.contrast / 100})`);
12
13
  // Saturation: slider 0-200, CSS saturate(0-2)
14
  parts.push(`saturate(${basic.saturation / 100})`);
15
16
  // Exposure: -100 to 100, maps to exponential brightness multiplier
17
  if (basic.exposure !== 0) {
18
    const exposureMult = Math.pow(2, basic.exposure / 100);
19
    parts.push(`brightness(${exposureMult})`);
20
  }
21
22
  // Temperature: -100 (cool) to 100 (warm)
23
  if (basic.temperature !== 0) {
24
    const absTemp = Math.abs(basic.temperature) / 100;
25
    const sepia = absTemp * 0.3;
26
    parts.push(`sepia(${sepia})`);
27
    if (basic.temperature < 0) {
28
      parts.push(`hue-rotate(180deg)`);
29
    }
30
    parts.push(`saturate(${1 + absTemp * 0.2})`);
31
  }
32
33
  // Tint: -100 to 100, maps to hue-rotate -30 to 30 degrees
34
  if (basic.tint !== 0) {
35
    const deg = (basic.tint / 100) * 30;
36
    parts.push(`hue-rotate(${deg}deg)`);
37
  }
38
39
  return parts.join(" ");
40
}
41
42
export interface LUTResult {
43
  r: Uint8Array;
44
  g: Uint8Array;
45
  b: Uint8Array;
46
  isIdentity: boolean;
47
}
48
49
export function buildLUT(basic: BasicFilters, curves: CurvesState): LUTResult {
50
  const curvesIdentity =
51
    isIdentityCurve(curves.rgb) &&
52
    isIdentityCurve(curves.r) &&
53
    isIdentityCurve(curves.g) &&
54
    isIdentityCurve(curves.b);
55
56
  const noHighlightsShadows = basic.highlights === 0 && basic.shadows === 0;
57
58
  if (curvesIdentity && noHighlightsShadows) {
59
    const identity = new Uint8Array(256);
60
    for (let i = 0; i < 256; i++) identity[i] = i;
61
    return { r: identity, g: identity, b: identity, isIdentity: true };
62
  }
63
64
  // Start with highlight/shadow adjustments
65
  const baseLut = new Uint8Array(256);
66
  for (let i = 0; i < 256; i++) {
67
    let val = i;
68
69
    // Shadows: gamma on lower half (values < 128)
70
    if (basic.shadows !== 0) {
71
      const shadowGamma = 1 - basic.shadows / 200;
72
      if (i < 128) {
73
        const t = i / 128;
74
        const adjusted = Math.pow(t, shadowGamma) * 128;
75
        val = adjusted + (val - i);
76
      }
77
    }
78
79
    // Highlights: gamma on upper half (values > 128)
80
    if (basic.highlights !== 0) {
81
      const highlightGamma = 1 - basic.highlights / 200;
82
      if (val > 128) {
83
        const t = (val - 128) / 127;
84
        val = 128 + Math.pow(t, highlightGamma) * 127;
85
      }
86
    }
87
88
    baseLut[i] = Math.max(0, Math.min(255, Math.round(val)));
89
  }
90
91
  // Apply curves on top
92
  const rgbLut = interpolateSpline(curves.rgb);
93
  const rCurve = interpolateSpline(curves.r);
94
  const gCurve = interpolateSpline(curves.g);
95
  const bCurve = interpolateSpline(curves.b);
96
97
  const r = new Uint8Array(256);
98
  const g = new Uint8Array(256);
99
  const b = new Uint8Array(256);
100
101
  for (let i = 0; i < 256; i++) {
102
    const base = baseLut[i];
103
    const afterRgb = rgbLut[base];
104
    r[i] = rCurve[afterRgb];
105
    g[i] = gCurve[afterRgb];
106
    b[i] = bCurve[afterRgb];
107
  }
108
109
  return { r, g, b, isIdentity: false };
110
}