rgb-curve 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +737 -0
- package/dist/index.d.ts +348 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -0
- package/dist/index.mjs +793 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +58 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { CSSProperties } from 'react';
|
|
2
|
+
import { ForwardRefExoticComponent } from 'react';
|
|
3
|
+
import { MouseEvent as MouseEvent_2 } from 'react';
|
|
4
|
+
import { NamedExoticComponent } from 'react';
|
|
5
|
+
import { RefAttributes } from 'react';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Apply LUT to RGB values
|
|
9
|
+
*/
|
|
10
|
+
export declare function applyLUT(r: number, g: number, b: number, lut: LUTData): [number, number, number];
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Catmull-Rom spline interpolation
|
|
14
|
+
* Smoother curves but may overshoot
|
|
15
|
+
*/
|
|
16
|
+
export declare function catmullRomInterpolation(points: CurvePoint[], x: number): number;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Available curve channels
|
|
20
|
+
*/
|
|
21
|
+
export declare type Channel = 'master' | 'red' | 'green' | 'blue';
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Channel colors for easy access
|
|
25
|
+
*/
|
|
26
|
+
export declare const CHANNEL_COLORS: {
|
|
27
|
+
readonly master: "#e0e0e0";
|
|
28
|
+
readonly red: "#ff6b6b";
|
|
29
|
+
readonly green: "#51cf66";
|
|
30
|
+
readonly blue: "#339af0";
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Channel display info
|
|
35
|
+
*/
|
|
36
|
+
export declare const CHANNEL_INFO: Record<Channel, {
|
|
37
|
+
label: string;
|
|
38
|
+
shortLabel: string;
|
|
39
|
+
}>;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Points for all channels
|
|
43
|
+
*/
|
|
44
|
+
export declare type ChannelPoints = Record<Channel, CurvePoint[]>;
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Get all channels as an array
|
|
48
|
+
*/
|
|
49
|
+
export declare const CHANNELS: Channel[];
|
|
50
|
+
|
|
51
|
+
export declare const ChannelTabs: NamedExoticComponent<ChannelTabsProps>;
|
|
52
|
+
|
|
53
|
+
declare interface ChannelTabsProps {
|
|
54
|
+
activeChannel: Channel;
|
|
55
|
+
onChange: (channel: Channel) => void;
|
|
56
|
+
style?: TabsStyle;
|
|
57
|
+
disabled?: boolean;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Clamp a value between min and max
|
|
62
|
+
*/
|
|
63
|
+
export declare function clamp(value: number, min: number, max: number): number;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Style configuration for control points
|
|
67
|
+
*/
|
|
68
|
+
export declare interface ControlPointStyle {
|
|
69
|
+
radius?: number;
|
|
70
|
+
fill?: string;
|
|
71
|
+
stroke?: string;
|
|
72
|
+
strokeWidth?: number;
|
|
73
|
+
activeFill?: string;
|
|
74
|
+
activeStroke?: string;
|
|
75
|
+
hoverScale?: number;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export declare const CurveCanvas: NamedExoticComponent<CurveCanvasProps>;
|
|
79
|
+
|
|
80
|
+
declare interface CurveCanvasProps {
|
|
81
|
+
width: number;
|
|
82
|
+
height: number;
|
|
83
|
+
points: CurvePoint[];
|
|
84
|
+
channel: Channel;
|
|
85
|
+
gridStyle?: GridStyle;
|
|
86
|
+
curveStyle?: CurveLineStyle;
|
|
87
|
+
controlPointStyle?: ControlPointStyle;
|
|
88
|
+
histogramStyle?: HistogramStyle;
|
|
89
|
+
histogramData?: Uint8Array;
|
|
90
|
+
wrapperStyle?: CSSProperties;
|
|
91
|
+
disabled?: boolean;
|
|
92
|
+
interpolation?: 'monotone' | 'catmullRom';
|
|
93
|
+
onAddPoint: (channel: Channel, point: CurvePoint) => void;
|
|
94
|
+
onRemovePoint: (channel: Channel, index: number) => void;
|
|
95
|
+
onUpdatePoint: (channel: Channel, index: number, point: CurvePoint) => void;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Data returned on onChange
|
|
100
|
+
*/
|
|
101
|
+
export declare interface CurveChangeData {
|
|
102
|
+
/** Current control points for all channels */
|
|
103
|
+
points: ChannelPoints;
|
|
104
|
+
/** Generated LUT for pixel processing */
|
|
105
|
+
lut: LUTData;
|
|
106
|
+
/** Currently active channel */
|
|
107
|
+
activeChannel: Channel;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Style configuration for curve lines
|
|
112
|
+
*/
|
|
113
|
+
export declare interface CurveLineStyle {
|
|
114
|
+
color?: string;
|
|
115
|
+
width?: number;
|
|
116
|
+
shadowColor?: string;
|
|
117
|
+
shadowBlur?: number;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* A point on the curve
|
|
122
|
+
*/
|
|
123
|
+
export declare interface CurvePoint {
|
|
124
|
+
x: number;
|
|
125
|
+
y: number;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Default curve height
|
|
130
|
+
*/
|
|
131
|
+
export declare const DEFAULT_HEIGHT = 300;
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Default styles - Dark theme inspired by Lightroom/Premiere Pro
|
|
135
|
+
*/
|
|
136
|
+
export declare const DEFAULT_STYLES: Required<RGBCurveStyles>;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Default curve width
|
|
140
|
+
*/
|
|
141
|
+
export declare const DEFAULT_WIDTH = 300;
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Generate LUT for a single channel
|
|
145
|
+
*/
|
|
146
|
+
export declare function generateChannelLUT(points: CurvePoint[], interpolation?: 'monotone' | 'catmullRom'): Uint8Array;
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Generate LUT for all channels
|
|
150
|
+
*/
|
|
151
|
+
export declare function generateLUT(channelPoints: ChannelPoints, interpolation?: 'monotone' | 'catmullRom'): LUTData;
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Get default points for all channels
|
|
155
|
+
*/
|
|
156
|
+
export declare function getDefaultChannelPoints(): ChannelPoints;
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Default points for a curve (diagonal line from 0,0 to 255,255)
|
|
160
|
+
*/
|
|
161
|
+
export declare function getDefaultPoints(): CurvePoint[];
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Style configuration for the grid
|
|
165
|
+
*/
|
|
166
|
+
export declare interface GridStyle {
|
|
167
|
+
color?: string;
|
|
168
|
+
lineWidth?: number;
|
|
169
|
+
subdivisions?: number;
|
|
170
|
+
showDiagonal?: boolean;
|
|
171
|
+
diagonalColor?: string;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Style configuration for histogram
|
|
176
|
+
*/
|
|
177
|
+
export declare interface HistogramStyle {
|
|
178
|
+
show?: boolean;
|
|
179
|
+
opacity?: number;
|
|
180
|
+
fillColor?: string;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* LUT (Look Up Table) for all channels - 256 values each
|
|
185
|
+
*/
|
|
186
|
+
export declare interface LUTData {
|
|
187
|
+
master: Uint8Array;
|
|
188
|
+
red: Uint8Array;
|
|
189
|
+
green: Uint8Array;
|
|
190
|
+
blue: Uint8Array;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Monotone cubic spline interpolation
|
|
195
|
+
* This ensures the curve doesn't overshoot between control points
|
|
196
|
+
* Based on Fritsch-Carlson method
|
|
197
|
+
*/
|
|
198
|
+
export declare function monotoneCubicInterpolation(points: CurvePoint[], x: number): number;
|
|
199
|
+
|
|
200
|
+
export declare const RGBCurve: ForwardRefExoticComponent<RGBCurveProps & RefAttributes<RGBCurveRef>>;
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Props for the RGBCurve component
|
|
204
|
+
*/
|
|
205
|
+
export declare interface RGBCurveProps {
|
|
206
|
+
/** Width of the curve editor */
|
|
207
|
+
width?: number;
|
|
208
|
+
/** Height of the curve editor */
|
|
209
|
+
height?: number;
|
|
210
|
+
/** Initial points for all channels */
|
|
211
|
+
defaultPoints?: Partial<ChannelPoints>;
|
|
212
|
+
/** Controlled points (makes component controlled) */
|
|
213
|
+
points?: Partial<ChannelPoints>;
|
|
214
|
+
/** Default active channel */
|
|
215
|
+
defaultChannel?: Channel;
|
|
216
|
+
/** Controlled active channel */
|
|
217
|
+
activeChannel?: Channel;
|
|
218
|
+
/** Callback when curve changes */
|
|
219
|
+
onChange?: (data: CurveChangeData) => void;
|
|
220
|
+
/** Callback when active channel changes */
|
|
221
|
+
onChannelChange?: (channel: Channel) => void;
|
|
222
|
+
/** Custom styles */
|
|
223
|
+
styles?: RGBCurveStyles;
|
|
224
|
+
/** Show/hide channel tabs */
|
|
225
|
+
showTabs?: boolean;
|
|
226
|
+
/** Show/hide histogram */
|
|
227
|
+
showHistogram?: boolean;
|
|
228
|
+
/** Histogram data (256 values) */
|
|
229
|
+
histogramData?: Uint8Array;
|
|
230
|
+
/** Disable interaction */
|
|
231
|
+
disabled?: boolean;
|
|
232
|
+
/** Class name for container */
|
|
233
|
+
className?: string;
|
|
234
|
+
/** Interpolation type */
|
|
235
|
+
interpolation?: 'monotone' | 'catmullRom';
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Ref methods exposed by RGBCurve
|
|
240
|
+
*/
|
|
241
|
+
export declare interface RGBCurveRef {
|
|
242
|
+
/** Reset all curves to default (diagonal) */
|
|
243
|
+
reset: () => void;
|
|
244
|
+
/** Reset a specific channel */
|
|
245
|
+
resetChannel: (channel: Channel) => void;
|
|
246
|
+
/** Get current LUT data */
|
|
247
|
+
getLUT: () => LUTData;
|
|
248
|
+
/** Get current points */
|
|
249
|
+
getPoints: () => ChannelPoints;
|
|
250
|
+
/** Set points programmatically */
|
|
251
|
+
setPoints: (points: Partial<ChannelPoints>) => void;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Complete style configuration
|
|
256
|
+
*/
|
|
257
|
+
export declare interface RGBCurveStyles {
|
|
258
|
+
/** Container styles */
|
|
259
|
+
container?: CSSProperties;
|
|
260
|
+
/** Canvas wrapper styles */
|
|
261
|
+
canvasWrapper?: CSSProperties;
|
|
262
|
+
/** Grid appearance */
|
|
263
|
+
grid?: GridStyle;
|
|
264
|
+
/** Curve line styles per channel */
|
|
265
|
+
curve?: {
|
|
266
|
+
master?: CurveLineStyle;
|
|
267
|
+
red?: CurveLineStyle;
|
|
268
|
+
green?: CurveLineStyle;
|
|
269
|
+
blue?: CurveLineStyle;
|
|
270
|
+
};
|
|
271
|
+
/** Control point appearance */
|
|
272
|
+
controlPoint?: ControlPointStyle;
|
|
273
|
+
/** Channel tabs appearance */
|
|
274
|
+
tabs?: TabsStyle;
|
|
275
|
+
/** Histogram appearance */
|
|
276
|
+
histogram?: HistogramStyle;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Sort points by x coordinate
|
|
281
|
+
*/
|
|
282
|
+
export declare function sortPoints(points: CurvePoint[]): CurvePoint[];
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Style configuration for channel tabs
|
|
286
|
+
*/
|
|
287
|
+
export declare interface TabsStyle {
|
|
288
|
+
background?: string;
|
|
289
|
+
borderRadius?: number;
|
|
290
|
+
gap?: number;
|
|
291
|
+
tab?: {
|
|
292
|
+
padding?: string;
|
|
293
|
+
borderRadius?: number;
|
|
294
|
+
fontSize?: number;
|
|
295
|
+
fontWeight?: number | string;
|
|
296
|
+
color?: string;
|
|
297
|
+
background?: string;
|
|
298
|
+
hoverBackground?: string;
|
|
299
|
+
activeColor?: string;
|
|
300
|
+
activeBackground?: string;
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
export declare function useCanvasInteraction(options: UseCanvasInteractionOptions): UseCanvasInteractionReturn;
|
|
305
|
+
|
|
306
|
+
declare interface UseCanvasInteractionOptions {
|
|
307
|
+
points: CurvePoint[];
|
|
308
|
+
channel: Channel;
|
|
309
|
+
width: number;
|
|
310
|
+
height: number;
|
|
311
|
+
disabled?: boolean;
|
|
312
|
+
onAddPoint: (channel: Channel, point: CurvePoint) => void;
|
|
313
|
+
onRemovePoint: (channel: Channel, index: number) => void;
|
|
314
|
+
onUpdatePoint: (channel: Channel, index: number, point: CurvePoint) => void;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
declare interface UseCanvasInteractionReturn {
|
|
318
|
+
activePointIndex: number | null;
|
|
319
|
+
hoveredPointIndex: number | null;
|
|
320
|
+
handleMouseDown: (e: MouseEvent_2<HTMLCanvasElement>) => void;
|
|
321
|
+
handleMouseMove: (e: MouseEvent_2<HTMLCanvasElement>) => void;
|
|
322
|
+
handleMouseUp: () => void;
|
|
323
|
+
handleMouseLeave: () => void;
|
|
324
|
+
handleDoubleClick: (e: MouseEvent_2<HTMLCanvasElement>) => void;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
export declare function useCurvePoints(options?: UseCurvePointsOptions): UseCurvePointsReturn;
|
|
328
|
+
|
|
329
|
+
declare interface UseCurvePointsOptions {
|
|
330
|
+
defaultPoints?: Partial<ChannelPoints>;
|
|
331
|
+
controlledPoints?: Partial<ChannelPoints>;
|
|
332
|
+
interpolation?: 'monotone' | 'catmullRom';
|
|
333
|
+
onChange?: (points: ChannelPoints, lut: LUTData) => void;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
declare interface UseCurvePointsReturn {
|
|
337
|
+
points: ChannelPoints;
|
|
338
|
+
lut: LUTData;
|
|
339
|
+
addPoint: (channel: Channel, point: CurvePoint) => void;
|
|
340
|
+
removePoint: (channel: Channel, index: number) => void;
|
|
341
|
+
updatePoint: (channel: Channel, index: number, point: CurvePoint) => void;
|
|
342
|
+
resetChannel: (channel: Channel) => void;
|
|
343
|
+
resetAll: () => void;
|
|
344
|
+
setChannelPoints: (channel: Channel, points: CurvePoint[]) => void;
|
|
345
|
+
setAllPoints: (points: Partial<ChannelPoints>) => void;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
export { }
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
"use strict";Object.defineProperty(exports,Symbol.toStringTag,{value:"Module"});const A=require("react/jsx-runtime"),d=require("react");function D(n,o,e){return Math.min(Math.max(n,o),e)}function N(){return[{x:0,y:0},{x:255,y:255}]}function B(){return{master:N(),red:N(),green:N(),blue:N()}}function I(n){return[...n].sort((o,e)=>o.x-e.x)}function Y(n,o){const e=I(n),r=e.length;if(r===0)return o;if(r===1||o<=e[0].x)return e[0].y;if(o>=e[r-1].x)return e[r-1].y;let t=0;for(;t<r-1&&e[t+1].x<o;)t++;const C=e[t].x,f=e[t+1].x,v=e[t].y,l=e[t+1].y,s=f-C,a=l-v;if(s===0)return v;let p,b;if(t===0)p=a/s;else{const y=C-e[t-1].x,g=v-e[t-1].y;p=y===0?0:(a/s+g/y)/2}if(t===r-2)b=a/s;else{const y=e[t+2].x-f,g=e[t+2].y-l;b=y===0?0:(a/s+g/y)/2}const h=a/s;if(h===0)p=0,b=0;else{const y=p/h,g=b/h;y<0&&(p=0),g<0&&(b=0);const i=y*y+g*g;if(i>9){const P=3/Math.sqrt(i);p=P*y*h,b=P*g*h}}const k=(o-C)/s,m=k*k,x=m*k,S=2*x-3*m+1,c=x-2*m+k,u=-2*x+3*m,w=x-m,L=S*v+c*s*p+u*l+w*s*b;return D(Math.round(L),0,255)}function X(n,o){const e=I(n),r=e.length;if(r===0)return o;if(r===1||o<=e[0].x)return e[0].y;if(o>=e[r-1].x)return e[r-1].y;let t=0;for(;t<r-1&&e[t+1].x<o;)t++;const C=e[Math.max(0,t-1)],f=e[t],v=e[Math.min(r-1,t+1)],l=e[Math.min(r-1,t+2)],s=v.x-f.x;if(s===0)return f.y;const a=(o-f.x)/s,p=a*a,b=p*a,h=.5*(2*f.y+(-C.y+v.y)*a+(2*C.y-5*f.y+4*v.y-l.y)*p+(-C.y+3*f.y-3*v.y+l.y)*b);return D(Math.round(h),0,255)}function F(n,o="monotone"){const e=new Uint8Array(256),r=o==="monotone"?Y:X;for(let t=0;t<256;t++)e[t]=r(n,t);return e}function O(n,o="monotone"){return{master:F(n.master,o),red:F(n.red,o),green:F(n.green,o),blue:F(n.blue,o)}}function se(n,o,e,r){let t=r.red[D(n,0,255)],C=r.green[D(o,0,255)],f=r.blue[D(e,0,255)];return t=r.master[t],C=r.master[C],f=r.master[f],[t,C,f]}function ae(n,o,e){const r=n.x-o.x,t=n.y-o.y;return Math.sqrt(r*r+t*t)<=e}const $=["master","red","green","blue"],J={master:{label:"Master",shortLabel:"RGB"},red:{label:"Red",shortLabel:"R"},green:{label:"Green",shortLabel:"G"},blue:{label:"Blue",shortLabel:"B"}},K=300,Q=300,T={container:{display:"flex",flexDirection:"column",gap:"12px",padding:"16px",backgroundColor:"#1a1a1a",borderRadius:"12px",fontFamily:'-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif'},canvasWrapper:{position:"relative",borderRadius:"8px",overflow:"hidden",backgroundColor:"#0d0d0d"},grid:{color:"#2a2a2a",lineWidth:1,subdivisions:4,showDiagonal:!0,diagonalColor:"#333333"},curve:{master:{color:"#e0e0e0",width:2,shadowColor:"rgba(255, 255, 255, 0.3)",shadowBlur:4},red:{color:"#ff6b6b",width:2,shadowColor:"rgba(255, 107, 107, 0.4)",shadowBlur:4},green:{color:"#51cf66",width:2,shadowColor:"rgba(81, 207, 102, 0.4)",shadowBlur:4},blue:{color:"#339af0",width:2,shadowColor:"rgba(51, 154, 240, 0.4)",shadowBlur:4}},controlPoint:{radius:6,fill:"#ffffff",stroke:"#000000",strokeWidth:2,activeFill:"#ffd43b",activeStroke:"#000000",hoverScale:1.2},tabs:{background:"#252525",borderRadius:8,gap:4,tab:{padding:"8px 16px",borderRadius:6,fontSize:13,fontWeight:500,color:"#808080",background:"transparent",hoverBackground:"#333333",activeColor:"#ffffff",activeBackground:"#404040"}},histogram:{show:!1,opacity:.3,fillColor:"#666666"}},V={master:"#e0e0e0",red:"#ff6b6b",green:"#51cf66",blue:"#339af0"},le=12,G=5;function Z(n){const{points:o,channel:e,width:r,height:t,disabled:C=!1,onAddPoint:f,onRemovePoint:v,onUpdatePoint:l}=n,[s,a]=d.useState(null),[p,b]=d.useState(null),h=d.useRef(!1),k=d.useCallback((g,i)=>({x:Math.round(g/r*255),y:Math.round((1-i/t)*255)}),[r,t]),m=d.useCallback((g,i)=>({x:g/255*r,y:(1-i/255)*t}),[r,t]),x=d.useCallback((g,i)=>{const P=I(o);for(let M=0;M<P.length;M++){const R=m(P[M].x,P[M].y);if(ae({x:g,y:i},R,le))return M}return null},[o,m]),S=d.useCallback(g=>{const i=g.currentTarget.getBoundingClientRect();return{x:g.clientX-i.left,y:g.clientY-i.top}},[]),c=d.useCallback(g=>{if(C)return;const i=S(g),P=x(i.x,i.y);if(P!==null)a(P),h.current=!0;else{const M=k(i.x,i.y);f(e,M)}},[C,S,x,k,e,f]),u=d.useCallback(g=>{if(C)return;const i=S(g);if(h.current&&s!==null){const P=k(i.x,i.y);l(e,s,P)}else{const P=x(i.x,i.y);b(P)}},[C,S,s,k,e,l,x]),w=d.useCallback(()=>{h.current=!1,a(null)},[]),L=d.useCallback(()=>{h.current=!1,a(null),b(null)},[]),y=d.useCallback(g=>{if(C)return;const i=S(g),P=x(i.x,i.y);P!==null&&v(e,P)},[C,S,x,e,v]);return d.useEffect(()=>{const g=()=>{h.current=!1,a(null)};return window.addEventListener("mouseup",g),()=>{window.removeEventListener("mouseup",g)}},[]),{activePointIndex:s,hoveredPointIndex:p,handleMouseDown:c,handleMouseMove:u,handleMouseUp:w,handleMouseLeave:L,handleDoubleClick:y}}const ee=d.memo(function({width:o,height:e,points:r,channel:t,gridStyle:C=T.grid,curveStyle:f,controlPointStyle:v=T.controlPoint,histogramStyle:l=T.histogram,histogramData:s,wrapperStyle:a=T.canvasWrapper,disabled:p=!1,interpolation:b="monotone",onAddPoint:h,onRemovePoint:k,onUpdatePoint:m}){const x=d.useRef(null),{activePointIndex:S,hoveredPointIndex:c,handleMouseDown:u,handleMouseMove:w,handleMouseUp:L,handleMouseLeave:y,handleDoubleClick:g}=Z({points:r,channel:t,width:o,height:e,disabled:p,onAddPoint:h,onRemovePoint:k,onUpdatePoint:m}),i=typeof window<"u"&&window.devicePixelRatio||1,P=d.useCallback(()=>{const M=x.current;if(!M)return;const R=M.getContext("2d");if(!R)return;R.clearRect(0,0,o*i,e*i),R.save(),R.scale(i,i),l!=null&&l.show&&s&&ie(R,s,l,o,e),ce(R,C,o,e),ue(R,r,f,o,e,b==="monotone"?Y:X),de(R,r,v,o,e,S,c),R.restore()},[o,e,i,r,C,f,v,l,s,b,S,c]);return d.useEffect(()=>{P()},[P]),d.useEffect(()=>{const M=x.current;M&&(M.width=o*i,M.height=e*i,M.style.width=`${o}px`,M.style.height=`${e}px`,P())},[o,e,i,P]),A.jsx("div",{style:a,children:A.jsx("canvas",{ref:x,style:{display:"block",cursor:p?"not-allowed":c!==null?"grab":"crosshair"},onMouseDown:u,onMouseMove:w,onMouseUp:L,onMouseLeave:y,onDoubleClick:g})})});function ce(n,o,e,r){const{color:t,lineWidth:C,subdivisions:f,showDiagonal:v,diagonalColor:l}={...T.grid,...o};n.strokeStyle=t,n.lineWidth=C;const s=e/f;for(let a=1;a<f;a++){const p=a*s;n.beginPath(),n.moveTo(p,0),n.lineTo(p,r),n.stroke(),n.beginPath(),n.moveTo(0,p),n.lineTo(e,p),n.stroke()}n.strokeRect(.5,.5,e-1,r-1),v&&(n.strokeStyle=l,n.setLineDash([4,4]),n.beginPath(),n.moveTo(0,r),n.lineTo(e,0),n.stroke(),n.setLineDash([]))}function ie(n,o,e,r,t){const{opacity:C,fillColor:f}={...T.histogram,...e};let v=0;for(let s=0;s<o.length;s++)o[s]>v&&(v=o[s]);if(v===0)return;n.fillStyle=f,n.globalAlpha=C;const l=r/256;for(let s=0;s<256;s++){const a=o[s]/v*t,p=s*l;n.fillRect(p,t-a,l,a)}n.globalAlpha=1}function ue(n,o,e,r,t,C){const f=I(o);if(f.length===0)return;const{color:v,width:l,shadowColor:s,shadowBlur:a}={...T.curve.master,...e};s&&a&&(n.shadowColor=s,n.shadowBlur=a),n.strokeStyle=v,n.lineWidth=l,n.lineCap="round",n.lineJoin="round",n.beginPath();for(let p=0;p<=r;p++){const b=p/r*255,h=C(f,b),k=t-h/255*t;p===0?n.moveTo(p,k):n.lineTo(p,k)}n.stroke(),n.shadowColor="transparent",n.shadowBlur=0}function de(n,o,e,r,t,C,f){const v=I(o),{radius:l,fill:s,stroke:a,strokeWidth:p,activeFill:b,activeStroke:h,hoverScale:k}={...T.controlPoint,...e};v.forEach((m,x)=>{const S=m.x/255*r,c=t-m.y/255*t,u=x===C,y=l*(x===f||u?k:1);n.beginPath(),n.arc(S,c,y,0,Math.PI*2),n.fillStyle=u?b:s,n.fill(),n.strokeStyle=u?h:a,n.lineWidth=p,n.stroke()})}const ne=d.memo(function({activeChannel:o,onChange:e,style:r,disabled:t=!1}){const[C,f]=d.useState(null),v={...T.tabs,...r,tab:{...T.tabs.tab,...r==null?void 0:r.tab}},l={display:"flex",alignItems:"center",gap:v.gap,padding:"4px",backgroundColor:v.background,borderRadius:v.borderRadius},s=d.useCallback(b=>{const h=b===o,k=b===C,m=v.tab;return{display:"flex",alignItems:"center",gap:"6px",padding:m.padding,borderRadius:m.borderRadius,fontSize:m.fontSize,fontWeight:m.fontWeight,color:h?m.activeColor:m.color,backgroundColor:h?m.activeBackground:k?m.hoverBackground:m.background,border:"none",cursor:t?"not-allowed":"pointer",transition:"all 0.15s ease",opacity:t?.5:1,outline:"none"}},[o,C,v.tab,t]),a=b=>{const h=V[b];return{width:"8px",height:"8px",borderRadius:"50%",backgroundColor:h,boxShadow:b===o?`0 0 6px ${h}`:"none",transition:"box-shadow 0.15s ease"}},p=d.useCallback(b=>{t||e(b)},[t,e]);return A.jsx("div",{style:l,role:"tablist",children:$.map(b=>A.jsxs("button",{role:"tab","aria-selected":b===o,"aria-disabled":t,style:s(b),onClick:()=>p(b),onMouseEnter:()=>f(b),onMouseLeave:()=>f(null),children:[A.jsx("span",{style:a(b)}),A.jsx("span",{children:J[b].label})]},b))})});function oe(n={}){const{defaultPoints:o,controlledPoints:e,interpolation:r="monotone",onChange:t}=n,C=d.useMemo(()=>{const c=B();return o?{master:o.master||c.master,red:o.red||c.red,green:o.green||c.green,blue:o.blue||c.blue}:c},[o]),[f,v]=d.useState(C),l=d.useMemo(()=>{if(e){const c=B();return{master:e.master||c.master,red:e.red||c.red,green:e.green||c.green,blue:e.blue||c.blue}}return f},[e,f]),s=d.useMemo(()=>O(l,r),[l,r]),a=d.useCallback(c=>{if(e||v(c),t){const u=O(c,r);t(c,u)}},[e,t,r]),p=d.useCallback((c,u)=>{const w=l[c],L=I(w);for(const i of L)if(Math.abs(i.x-u.x)<G)return;const y=I([...w,u]),g={...l,[c]:y};a(g)},[l,a]),b=d.useCallback((c,u)=>{const w=l[c];if(w.length<=2)return;const L=I(w);if(u===0||u===L.length-1)return;const y=L.filter((i,P)=>P!==u),g={...l,[c]:y};a(g)},[l,a]),h=d.useCallback((c,u,w)=>{const L=I(l[c]),y=u===0,g=u===L.length-1;let i=w.x;if(y)i=0;else if(g)i=255;else{const E=L[u-1].x+G,W=L[u+1].x-G;i=D(i,E,W)}const P=D(w.y,0,255),M=L.map((E,W)=>W===u?{x:i,y:P}:E),R={...l,[c]:M};a(R)},[l,a]),k=d.useCallback(c=>{const u=B(),w={...l,[c]:u[c]};a(w)},[l,a]),m=d.useCallback(()=>{const c=B();a(c)},[a]),x=d.useCallback((c,u)=>{const w={...l,[c]:I(u)};a(w)},[l,a]),S=d.useCallback(c=>{const u=B(),w={master:I(c.master||u.master),red:I(c.red||u.red),green:I(c.green||u.green),blue:I(c.blue||u.blue)};a(w)},[a]);return{points:l,lut:s,addPoint:p,removePoint:b,updatePoint:h,resetChannel:k,resetAll:m,setChannelPoints:x,setAllPoints:S}}const fe=d.forwardRef(function({width:o=K,height:e=Q,defaultPoints:r,points:t,defaultChannel:C="master",activeChannel:f,onChange:v,onChannelChange:l,styles:s={},showTabs:a=!0,showHistogram:p=!1,histogramData:b,disabled:h=!1,className:k,interpolation:m="monotone"},x){const[S,c]=d.useState(C),u=f??S,w=d.useCallback((U,_)=>{v&&v({points:U,lut:_,activeChannel:u})},[v,u]),{points:L,lut:y,addPoint:g,removePoint:i,updatePoint:P,resetChannel:M,resetAll:R,setAllPoints:E}=oe({defaultPoints:r,controlledPoints:t,interpolation:m,onChange:w}),W=d.useCallback(U=>{f||c(U),l&&l(U)},[f,l]);d.useImperativeHandle(x,()=>({reset:R,resetChannel:M,getLUT:()=>y,getPoints:()=>L,setPoints:E}),[R,M,y,L,E]);const H=d.useMemo(()=>{var U,_,j,q,z;return{container:{...T.container,...s.container},canvasWrapper:{...T.canvasWrapper,...s.canvasWrapper},grid:{...T.grid,...s.grid},curve:{master:{...T.curve.master,...(U=s.curve)==null?void 0:U.master},red:{...T.curve.red,...(_=s.curve)==null?void 0:_.red},green:{...T.curve.green,...(j=s.curve)==null?void 0:j.green},blue:{...T.curve.blue,...(q=s.curve)==null?void 0:q.blue}},controlPoint:{...T.controlPoint,...s.controlPoint},tabs:{...T.tabs,...s.tabs,tab:{...T.tabs.tab,...(z=s.tabs)==null?void 0:z.tab}},histogram:{...T.histogram,...s.histogram}}},[s]),te=H.curve[u],re={...H.container,width:"fit-content"};return A.jsxs("div",{style:re,className:k,children:[a&&A.jsx(ne,{activeChannel:u,onChange:W,style:H.tabs,disabled:h}),A.jsx(ee,{width:o,height:e,points:L[u],channel:u,gridStyle:H.grid,curveStyle:te,controlPointStyle:H.controlPoint,histogramStyle:{...H.histogram,show:p},histogramData:b,wrapperStyle:H.canvasWrapper,disabled:h,interpolation:m,onAddPoint:g,onRemovePoint:i,onUpdatePoint:P})]})});exports.CHANNELS=$;exports.CHANNEL_COLORS=V;exports.CHANNEL_INFO=J;exports.ChannelTabs=ne;exports.CurveCanvas=ee;exports.DEFAULT_HEIGHT=Q;exports.DEFAULT_STYLES=T;exports.DEFAULT_WIDTH=K;exports.RGBCurve=fe;exports.applyLUT=se;exports.catmullRomInterpolation=X;exports.clamp=D;exports.generateChannelLUT=F;exports.generateLUT=O;exports.getDefaultChannelPoints=B;exports.getDefaultPoints=N;exports.monotoneCubicInterpolation=Y;exports.sortPoints=I;exports.useCanvasInteraction=Z;exports.useCurvePoints=oe;
|
|
2
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sources":["../src/utils/curve.ts","../src/utils/constants.ts","../src/hooks/useCanvasInteraction.ts","../src/components/CurveCanvas.tsx","../src/components/ChannelTabs.tsx","../src/hooks/useCurvePoints.ts","../src/components/RGBCurve.tsx"],"sourcesContent":["import { CurvePoint, LUTData, ChannelPoints, Channel } from '../types';\n\n/**\n * Clamp a value between min and max\n */\nexport function clamp(value: number, min: number, max: number): number {\n return Math.min(Math.max(value, min), max);\n}\n\n/**\n * Default points for a curve (diagonal line from 0,0 to 255,255)\n */\nexport function getDefaultPoints(): CurvePoint[] {\n return [\n { x: 0, y: 0 },\n { x: 255, y: 255 },\n ];\n}\n\n/**\n * Get default points for all channels\n */\nexport function getDefaultChannelPoints(): ChannelPoints {\n return {\n master: getDefaultPoints(),\n red: getDefaultPoints(),\n green: getDefaultPoints(),\n blue: getDefaultPoints(),\n };\n}\n\n/**\n * Sort points by x coordinate\n */\nexport function sortPoints(points: CurvePoint[]): CurvePoint[] {\n return [...points].sort((a, b) => a.x - b.x);\n}\n\n/**\n * Monotone cubic spline interpolation\n * This ensures the curve doesn't overshoot between control points\n * Based on Fritsch-Carlson method\n */\nexport function monotoneCubicInterpolation(\n points: CurvePoint[],\n x: number\n): number {\n const sorted = sortPoints(points);\n const n = sorted.length;\n\n if (n === 0) return x;\n if (n === 1) return sorted[0].y;\n\n // Handle edge cases\n if (x <= sorted[0].x) return sorted[0].y;\n if (x >= sorted[n - 1].x) return sorted[n - 1].y;\n\n // Find the segment containing x\n let i = 0;\n while (i < n - 1 && sorted[i + 1].x < x) {\n i++;\n }\n\n const x0 = sorted[i].x;\n const x1 = sorted[i + 1].x;\n const y0 = sorted[i].y;\n const y1 = sorted[i + 1].y;\n\n // Calculate slopes\n const dx = x1 - x0;\n const dy = y1 - y0;\n\n if (dx === 0) return y0;\n\n // Calculate tangents using finite differences\n let m0: number, m1: number;\n\n if (i === 0) {\n m0 = dy / dx;\n } else {\n const prevDx = x0 - sorted[i - 1].x;\n const prevDy = y0 - sorted[i - 1].y;\n m0 = prevDx === 0 ? 0 : (dy / dx + prevDy / prevDx) / 2;\n }\n\n if (i === n - 2) {\n m1 = dy / dx;\n } else {\n const nextDx = sorted[i + 2].x - x1;\n const nextDy = sorted[i + 2].y - y1;\n m1 = nextDx === 0 ? 0 : (dy / dx + nextDy / nextDx) / 2;\n }\n\n // Ensure monotonicity\n const slope = dy / dx;\n if (slope === 0) {\n m0 = 0;\n m1 = 0;\n } else {\n const alpha = m0 / slope;\n const beta = m1 / slope;\n\n // Fritsch-Carlson condition for monotonicity\n if (alpha < 0) m0 = 0;\n if (beta < 0) m1 = 0;\n\n const sum = alpha * alpha + beta * beta;\n if (sum > 9) {\n const tau = 3 / Math.sqrt(sum);\n m0 = tau * alpha * slope;\n m1 = tau * beta * slope;\n }\n }\n\n // Hermite interpolation\n const t = (x - x0) / dx;\n const t2 = t * t;\n const t3 = t2 * t;\n\n const h00 = 2 * t3 - 3 * t2 + 1;\n const h10 = t3 - 2 * t2 + t;\n const h01 = -2 * t3 + 3 * t2;\n const h11 = t3 - t2;\n\n const result = h00 * y0 + h10 * dx * m0 + h01 * y1 + h11 * dx * m1;\n\n return clamp(Math.round(result), 0, 255);\n}\n\n/**\n * Catmull-Rom spline interpolation\n * Smoother curves but may overshoot\n */\nexport function catmullRomInterpolation(\n points: CurvePoint[],\n x: number\n): number {\n const sorted = sortPoints(points);\n const n = sorted.length;\n\n if (n === 0) return x;\n if (n === 1) return sorted[0].y;\n\n // Handle edge cases\n if (x <= sorted[0].x) return sorted[0].y;\n if (x >= sorted[n - 1].x) return sorted[n - 1].y;\n\n // Find the segment containing x\n let i = 0;\n while (i < n - 1 && sorted[i + 1].x < x) {\n i++;\n }\n\n // Get 4 points for Catmull-Rom (p0, p1, p2, p3)\n const p0 = sorted[Math.max(0, i - 1)];\n const p1 = sorted[i];\n const p2 = sorted[Math.min(n - 1, i + 1)];\n const p3 = sorted[Math.min(n - 1, i + 2)];\n\n const dx = p2.x - p1.x;\n if (dx === 0) return p1.y;\n\n const t = (x - p1.x) / dx;\n const t2 = t * t;\n const t3 = t2 * t;\n\n // Catmull-Rom matrix multiplication\n const result =\n 0.5 *\n (2 * p1.y +\n (-p0.y + p2.y) * t +\n (2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * t2 +\n (-p0.y + 3 * p1.y - 3 * p2.y + p3.y) * t3);\n\n return clamp(Math.round(result), 0, 255);\n}\n\n/**\n * Generate LUT for a single channel\n */\nexport function generateChannelLUT(\n points: CurvePoint[],\n interpolation: 'monotone' | 'catmullRom' = 'monotone'\n): Uint8Array {\n const lut = new Uint8Array(256);\n const interpolate =\n interpolation === 'monotone'\n ? monotoneCubicInterpolation\n : catmullRomInterpolation;\n\n for (let i = 0; i < 256; i++) {\n lut[i] = interpolate(points, i);\n }\n\n return lut;\n}\n\n/**\n * Generate LUT for all channels\n */\nexport function generateLUT(\n channelPoints: ChannelPoints,\n interpolation: 'monotone' | 'catmullRom' = 'monotone'\n): LUTData {\n return {\n master: generateChannelLUT(channelPoints.master, interpolation),\n red: generateChannelLUT(channelPoints.red, interpolation),\n green: generateChannelLUT(channelPoints.green, interpolation),\n blue: generateChannelLUT(channelPoints.blue, interpolation),\n };\n}\n\n/**\n * Apply LUT to RGB values\n */\nexport function applyLUT(\n r: number,\n g: number,\n b: number,\n lut: LUTData\n): [number, number, number] {\n // Apply individual channel LUTs first\n let newR = lut.red[clamp(r, 0, 255)];\n let newG = lut.green[clamp(g, 0, 255)];\n let newB = lut.blue[clamp(b, 0, 255)];\n\n // Then apply master LUT to all\n newR = lut.master[newR];\n newG = lut.master[newG];\n newB = lut.master[newB];\n\n return [newR, newG, newB];\n}\n\n/**\n * Find the index where a new point should be inserted\n */\nexport function findInsertIndex(points: CurvePoint[], x: number): number {\n const sorted = sortPoints(points);\n for (let i = 0; i < sorted.length; i++) {\n if (sorted[i].x > x) return i;\n }\n return sorted.length;\n}\n\n/**\n * Check if a point is near another point (for hit testing)\n */\nexport function isPointNear(\n p1: { x: number; y: number },\n p2: { x: number; y: number },\n threshold: number\n): boolean {\n const dx = p1.x - p2.x;\n const dy = p1.y - p2.y;\n return Math.sqrt(dx * dx + dy * dy) <= threshold;\n}\n\n/**\n * Get all channels as an array\n */\nexport const CHANNELS: Channel[] = ['master', 'red', 'green', 'blue'];\n\n/**\n * Channel display info\n */\nexport const CHANNEL_INFO: Record<Channel, { label: string; shortLabel: string }> = {\n master: { label: 'Master', shortLabel: 'RGB' },\n red: { label: 'Red', shortLabel: 'R' },\n green: { label: 'Green', shortLabel: 'G' },\n blue: { label: 'Blue', shortLabel: 'B' },\n};\n","import { RGBCurveStyles } from '../types';\n\n/**\n * Default curve width\n */\nexport const DEFAULT_WIDTH = 300;\n\n/**\n * Default curve height\n */\nexport const DEFAULT_HEIGHT = 300;\n\n/**\n * Default styles - Dark theme inspired by Lightroom/Premiere Pro\n */\nexport const DEFAULT_STYLES: Required<RGBCurveStyles> = {\n container: {\n display: 'flex',\n flexDirection: 'column',\n gap: '12px',\n padding: '16px',\n backgroundColor: '#1a1a1a',\n borderRadius: '12px',\n fontFamily:\n '-apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, sans-serif',\n },\n canvasWrapper: {\n position: 'relative',\n borderRadius: '8px',\n overflow: 'hidden',\n backgroundColor: '#0d0d0d',\n },\n grid: {\n color: '#2a2a2a',\n lineWidth: 1,\n subdivisions: 4,\n showDiagonal: true,\n diagonalColor: '#333333',\n },\n curve: {\n master: {\n color: '#e0e0e0',\n width: 2,\n shadowColor: 'rgba(255, 255, 255, 0.3)',\n shadowBlur: 4,\n },\n red: {\n color: '#ff6b6b',\n width: 2,\n shadowColor: 'rgba(255, 107, 107, 0.4)',\n shadowBlur: 4,\n },\n green: {\n color: '#51cf66',\n width: 2,\n shadowColor: 'rgba(81, 207, 102, 0.4)',\n shadowBlur: 4,\n },\n blue: {\n color: '#339af0',\n width: 2,\n shadowColor: 'rgba(51, 154, 240, 0.4)',\n shadowBlur: 4,\n },\n },\n controlPoint: {\n radius: 6,\n fill: '#ffffff',\n stroke: '#000000',\n strokeWidth: 2,\n activeFill: '#ffd43b',\n activeStroke: '#000000',\n hoverScale: 1.2,\n },\n tabs: {\n background: '#252525',\n borderRadius: 8,\n gap: 4,\n tab: {\n padding: '8px 16px',\n borderRadius: 6,\n fontSize: 13,\n fontWeight: 500,\n color: '#808080',\n background: 'transparent',\n hoverBackground: '#333333',\n activeColor: '#ffffff',\n activeBackground: '#404040',\n },\n },\n histogram: {\n show: false,\n opacity: 0.3,\n fillColor: '#666666',\n },\n};\n\n/**\n * Channel colors for easy access\n */\nexport const CHANNEL_COLORS = {\n master: '#e0e0e0',\n red: '#ff6b6b',\n green: '#51cf66',\n blue: '#339af0',\n} as const;\n\n/**\n * Hit test threshold for control points (in pixels)\n */\nexport const POINT_HIT_THRESHOLD = 12;\n\n/**\n * Minimum distance between points (in x-axis)\n */\nexport const MIN_POINT_DISTANCE = 5;\n","import { useState, useCallback, useRef, useEffect, MouseEvent } from 'react';\nimport { CurvePoint, Channel } from '../types';\nimport { sortPoints, isPointNear } from '../utils/curve';\nimport { POINT_HIT_THRESHOLD } from '../utils/constants';\n\ninterface UseCanvasInteractionOptions {\n points: CurvePoint[];\n channel: Channel;\n width: number;\n height: number;\n disabled?: boolean;\n onAddPoint: (channel: Channel, point: CurvePoint) => void;\n onRemovePoint: (channel: Channel, index: number) => void;\n onUpdatePoint: (channel: Channel, index: number, point: CurvePoint) => void;\n}\n\ninterface UseCanvasInteractionReturn {\n activePointIndex: number | null;\n hoveredPointIndex: number | null;\n handleMouseDown: (e: MouseEvent<HTMLCanvasElement>) => void;\n handleMouseMove: (e: MouseEvent<HTMLCanvasElement>) => void;\n handleMouseUp: () => void;\n handleMouseLeave: () => void;\n handleDoubleClick: (e: MouseEvent<HTMLCanvasElement>) => void;\n}\n\nexport function useCanvasInteraction(\n options: UseCanvasInteractionOptions\n): UseCanvasInteractionReturn {\n const {\n points,\n channel,\n width,\n height,\n disabled = false,\n onAddPoint,\n onRemovePoint,\n onUpdatePoint,\n } = options;\n\n const [activePointIndex, setActivePointIndex] = useState<number | null>(null);\n const [hoveredPointIndex, setHoveredPointIndex] = useState<number | null>(\n null\n );\n const isDragging = useRef(false);\n\n // Convert canvas coordinates to curve coordinates (0-255)\n const canvasToCurve = useCallback(\n (canvasX: number, canvasY: number): CurvePoint => {\n return {\n x: Math.round((canvasX / width) * 255),\n y: Math.round((1 - canvasY / height) * 255), // Flip Y axis\n };\n },\n [width, height]\n );\n\n // Convert curve coordinates to canvas coordinates\n const curveToCanvas = useCallback(\n (curveX: number, curveY: number): { x: number; y: number } => {\n return {\n x: (curveX / 255) * width,\n y: (1 - curveY / 255) * height, // Flip Y axis\n };\n },\n [width, height]\n );\n\n // Find point at given canvas position\n const findPointAtPosition = useCallback(\n (canvasX: number, canvasY: number): number | null => {\n const sorted = sortPoints(points);\n\n for (let i = 0; i < sorted.length; i++) {\n const canvasPoint = curveToCanvas(sorted[i].x, sorted[i].y);\n if (\n isPointNear(\n { x: canvasX, y: canvasY },\n canvasPoint,\n POINT_HIT_THRESHOLD\n )\n ) {\n return i;\n }\n }\n\n return null;\n },\n [points, curveToCanvas]\n );\n\n // Get canvas position from mouse event\n const getCanvasPosition = useCallback(\n (e: MouseEvent<HTMLCanvasElement>): { x: number; y: number } => {\n const rect = e.currentTarget.getBoundingClientRect();\n return {\n x: e.clientX - rect.left,\n y: e.clientY - rect.top,\n };\n },\n []\n );\n\n const handleMouseDown = useCallback(\n (e: MouseEvent<HTMLCanvasElement>) => {\n if (disabled) return;\n\n const pos = getCanvasPosition(e);\n const pointIndex = findPointAtPosition(pos.x, pos.y);\n\n if (pointIndex !== null) {\n // Start dragging existing point\n setActivePointIndex(pointIndex);\n isDragging.current = true;\n } else {\n // Add new point\n const curvePoint = canvasToCurve(pos.x, pos.y);\n onAddPoint(channel, curvePoint);\n }\n },\n [\n disabled,\n getCanvasPosition,\n findPointAtPosition,\n canvasToCurve,\n channel,\n onAddPoint,\n ]\n );\n\n const handleMouseMove = useCallback(\n (e: MouseEvent<HTMLCanvasElement>) => {\n if (disabled) return;\n\n const pos = getCanvasPosition(e);\n\n if (isDragging.current && activePointIndex !== null) {\n // Dragging a point\n const curvePoint = canvasToCurve(pos.x, pos.y);\n onUpdatePoint(channel, activePointIndex, curvePoint);\n } else {\n // Check for hover\n const pointIndex = findPointAtPosition(pos.x, pos.y);\n setHoveredPointIndex(pointIndex);\n }\n },\n [\n disabled,\n getCanvasPosition,\n activePointIndex,\n canvasToCurve,\n channel,\n onUpdatePoint,\n findPointAtPosition,\n ]\n );\n\n const handleMouseUp = useCallback(() => {\n isDragging.current = false;\n setActivePointIndex(null);\n }, []);\n\n const handleMouseLeave = useCallback(() => {\n isDragging.current = false;\n setActivePointIndex(null);\n setHoveredPointIndex(null);\n }, []);\n\n const handleDoubleClick = useCallback(\n (e: MouseEvent<HTMLCanvasElement>) => {\n if (disabled) return;\n\n const pos = getCanvasPosition(e);\n const pointIndex = findPointAtPosition(pos.x, pos.y);\n\n if (pointIndex !== null) {\n onRemovePoint(channel, pointIndex);\n }\n },\n [disabled, getCanvasPosition, findPointAtPosition, channel, onRemovePoint]\n );\n\n // Handle global mouse up (in case mouse leaves canvas while dragging)\n useEffect(() => {\n const handleGlobalMouseUp = () => {\n isDragging.current = false;\n setActivePointIndex(null);\n };\n\n window.addEventListener('mouseup', handleGlobalMouseUp);\n return () => {\n window.removeEventListener('mouseup', handleGlobalMouseUp);\n };\n }, []);\n\n return {\n activePointIndex,\n hoveredPointIndex,\n handleMouseDown,\n handleMouseMove,\n handleMouseUp,\n handleMouseLeave,\n handleDoubleClick,\n };\n}\n","import {\n useRef,\n useEffect,\n useCallback,\n memo,\n CSSProperties,\n} from 'react';\nimport {\n CurvePoint,\n Channel,\n GridStyle,\n CurveLineStyle,\n ControlPointStyle,\n HistogramStyle,\n} from '../types';\nimport {\n sortPoints,\n monotoneCubicInterpolation,\n catmullRomInterpolation,\n} from '../utils/curve';\nimport { useCanvasInteraction } from '../hooks/useCanvasInteraction';\nimport { DEFAULT_STYLES } from '../utils/constants';\n\ninterface CurveCanvasProps {\n width: number;\n height: number;\n points: CurvePoint[];\n channel: Channel;\n gridStyle?: GridStyle;\n curveStyle?: CurveLineStyle;\n controlPointStyle?: ControlPointStyle;\n histogramStyle?: HistogramStyle;\n histogramData?: Uint8Array;\n wrapperStyle?: CSSProperties;\n disabled?: boolean;\n interpolation?: 'monotone' | 'catmullRom';\n onAddPoint: (channel: Channel, point: CurvePoint) => void;\n onRemovePoint: (channel: Channel, index: number) => void;\n onUpdatePoint: (channel: Channel, index: number, point: CurvePoint) => void;\n}\n\nexport const CurveCanvas = memo(function CurveCanvas({\n width,\n height,\n points,\n channel,\n gridStyle = DEFAULT_STYLES.grid,\n curveStyle,\n controlPointStyle = DEFAULT_STYLES.controlPoint,\n histogramStyle = DEFAULT_STYLES.histogram,\n histogramData,\n wrapperStyle = DEFAULT_STYLES.canvasWrapper,\n disabled = false,\n interpolation = 'monotone',\n onAddPoint,\n onRemovePoint,\n onUpdatePoint,\n}: CurveCanvasProps) {\n const canvasRef = useRef<HTMLCanvasElement>(null);\n\n const {\n activePointIndex,\n hoveredPointIndex,\n handleMouseDown,\n handleMouseMove,\n handleMouseUp,\n handleMouseLeave,\n handleDoubleClick,\n } = useCanvasInteraction({\n points,\n channel,\n width,\n height,\n disabled,\n onAddPoint,\n onRemovePoint,\n onUpdatePoint,\n });\n\n // Get device pixel ratio for sharp rendering\n const dpr = typeof window !== 'undefined' ? window.devicePixelRatio || 1 : 1;\n\n // Draw the canvas\n const draw = useCallback(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n const ctx = canvas.getContext('2d');\n if (!ctx) return;\n\n // Clear canvas\n ctx.clearRect(0, 0, width * dpr, height * dpr);\n\n // Scale for device pixel ratio\n ctx.save();\n ctx.scale(dpr, dpr);\n\n // Draw histogram if provided\n if (histogramStyle?.show && histogramData) {\n drawHistogram(ctx, histogramData, histogramStyle, width, height);\n }\n\n // Draw grid\n drawGrid(ctx, gridStyle, width, height);\n\n // Draw curve\n const interpolateFn =\n interpolation === 'monotone'\n ? monotoneCubicInterpolation\n : catmullRomInterpolation;\n drawCurve(ctx, points, curveStyle, width, height, interpolateFn);\n\n // Draw control points\n drawControlPoints(\n ctx,\n points,\n controlPointStyle,\n width,\n height,\n activePointIndex,\n hoveredPointIndex\n );\n\n ctx.restore();\n }, [\n width,\n height,\n dpr,\n points,\n gridStyle,\n curveStyle,\n controlPointStyle,\n histogramStyle,\n histogramData,\n interpolation,\n activePointIndex,\n hoveredPointIndex,\n ]);\n\n // Redraw on changes\n useEffect(() => {\n draw();\n }, [draw]);\n\n // Set canvas size\n useEffect(() => {\n const canvas = canvasRef.current;\n if (!canvas) return;\n\n canvas.width = width * dpr;\n canvas.height = height * dpr;\n canvas.style.width = `${width}px`;\n canvas.style.height = `${height}px`;\n\n draw();\n }, [width, height, dpr, draw]);\n\n return (\n <div style={wrapperStyle}>\n <canvas\n ref={canvasRef}\n style={{\n display: 'block',\n cursor: disabled\n ? 'not-allowed'\n : hoveredPointIndex !== null\n ? 'grab'\n : 'crosshair',\n }}\n onMouseDown={handleMouseDown}\n onMouseMove={handleMouseMove}\n onMouseUp={handleMouseUp}\n onMouseLeave={handleMouseLeave}\n onDoubleClick={handleDoubleClick}\n />\n </div>\n );\n});\n\n// Helper function to draw the grid\nfunction drawGrid(\n ctx: CanvasRenderingContext2D,\n style: GridStyle,\n width: number,\n height: number\n) {\n const { color, lineWidth, subdivisions, showDiagonal, diagonalColor } = {\n ...DEFAULT_STYLES.grid,\n ...style,\n };\n\n ctx.strokeStyle = color!;\n ctx.lineWidth = lineWidth!;\n\n // Draw vertical and horizontal lines\n const step = width / subdivisions!;\n for (let i = 1; i < subdivisions!; i++) {\n const pos = i * step;\n\n // Vertical line\n ctx.beginPath();\n ctx.moveTo(pos, 0);\n ctx.lineTo(pos, height);\n ctx.stroke();\n\n // Horizontal line\n ctx.beginPath();\n ctx.moveTo(0, pos);\n ctx.lineTo(width, pos);\n ctx.stroke();\n }\n\n // Draw border\n ctx.strokeRect(0.5, 0.5, width - 1, height - 1);\n\n // Draw diagonal (baseline)\n if (showDiagonal) {\n ctx.strokeStyle = diagonalColor!;\n ctx.setLineDash([4, 4]);\n ctx.beginPath();\n ctx.moveTo(0, height);\n ctx.lineTo(width, 0);\n ctx.stroke();\n ctx.setLineDash([]);\n }\n}\n\n// Helper function to draw the histogram\nfunction drawHistogram(\n ctx: CanvasRenderingContext2D,\n data: Uint8Array,\n style: HistogramStyle,\n width: number,\n height: number\n) {\n const { opacity, fillColor } = { ...DEFAULT_STYLES.histogram, ...style };\n\n // Find max value for normalization\n let max = 0;\n for (let i = 0; i < data.length; i++) {\n if (data[i] > max) max = data[i];\n }\n\n if (max === 0) return;\n\n ctx.fillStyle = fillColor!;\n ctx.globalAlpha = opacity!;\n\n const barWidth = width / 256;\n\n for (let i = 0; i < 256; i++) {\n const barHeight = (data[i] / max) * height;\n const x = i * barWidth;\n ctx.fillRect(x, height - barHeight, barWidth, barHeight);\n }\n\n ctx.globalAlpha = 1;\n}\n\n// Helper function to draw the curve\nfunction drawCurve(\n ctx: CanvasRenderingContext2D,\n points: CurvePoint[],\n style: CurveLineStyle | undefined,\n width: number,\n height: number,\n interpolate: (points: CurvePoint[], x: number) => number\n) {\n const sorted = sortPoints(points);\n if (sorted.length === 0) return;\n\n const { color, width: lineWidth, shadowColor, shadowBlur } = {\n ...DEFAULT_STYLES.curve.master,\n ...style,\n };\n\n // Set up shadow\n if (shadowColor && shadowBlur) {\n ctx.shadowColor = shadowColor;\n ctx.shadowBlur = shadowBlur;\n }\n\n ctx.strokeStyle = color!;\n ctx.lineWidth = lineWidth!;\n ctx.lineCap = 'round';\n ctx.lineJoin = 'round';\n\n ctx.beginPath();\n\n // Draw curve by interpolating each x position\n for (let px = 0; px <= width; px++) {\n const curveX = (px / width) * 255;\n const curveY = interpolate(sorted, curveX);\n const canvasY = height - (curveY / 255) * height;\n\n if (px === 0) {\n ctx.moveTo(px, canvasY);\n } else {\n ctx.lineTo(px, canvasY);\n }\n }\n\n ctx.stroke();\n\n // Reset shadow\n ctx.shadowColor = 'transparent';\n ctx.shadowBlur = 0;\n}\n\n// Helper function to draw control points\nfunction drawControlPoints(\n ctx: CanvasRenderingContext2D,\n points: CurvePoint[],\n style: ControlPointStyle,\n width: number,\n height: number,\n activeIndex: number | null,\n hoveredIndex: number | null\n) {\n const sorted = sortPoints(points);\n const {\n radius,\n fill,\n stroke,\n strokeWidth,\n activeFill,\n activeStroke,\n hoverScale,\n } = {\n ...DEFAULT_STYLES.controlPoint,\n ...style,\n };\n\n sorted.forEach((point, index) => {\n const canvasX = (point.x / 255) * width;\n const canvasY = height - (point.y / 255) * height;\n\n const isActive = index === activeIndex;\n const isHovered = index === hoveredIndex;\n const scale = isHovered || isActive ? hoverScale! : 1;\n const currentRadius = radius! * scale;\n\n ctx.beginPath();\n ctx.arc(canvasX, canvasY, currentRadius, 0, Math.PI * 2);\n\n // Fill\n ctx.fillStyle = isActive ? activeFill! : fill!;\n ctx.fill();\n\n // Stroke\n ctx.strokeStyle = isActive ? activeStroke! : stroke!;\n ctx.lineWidth = strokeWidth!;\n ctx.stroke();\n });\n}\n","import { memo, useState, useCallback, CSSProperties } from 'react';\nimport { Channel, TabsStyle } from '../types';\nimport { CHANNELS, CHANNEL_INFO } from '../utils/curve';\nimport { DEFAULT_STYLES, CHANNEL_COLORS } from '../utils/constants';\n\ninterface ChannelTabsProps {\n activeChannel: Channel;\n onChange: (channel: Channel) => void;\n style?: TabsStyle;\n disabled?: boolean;\n}\n\nexport const ChannelTabs = memo(function ChannelTabs({\n activeChannel,\n onChange,\n style,\n disabled = false,\n}: ChannelTabsProps) {\n const [hoveredChannel, setHoveredChannel] = useState<Channel | null>(null);\n\n const mergedStyle = {\n ...DEFAULT_STYLES.tabs,\n ...style,\n tab: {\n ...DEFAULT_STYLES.tabs.tab,\n ...style?.tab,\n },\n };\n\n const containerStyle: CSSProperties = {\n display: 'flex',\n alignItems: 'center',\n gap: mergedStyle.gap,\n padding: '4px',\n backgroundColor: mergedStyle.background,\n borderRadius: mergedStyle.borderRadius,\n };\n\n const getTabStyle = useCallback(\n (channel: Channel): CSSProperties => {\n const isActive = channel === activeChannel;\n const isHovered = channel === hoveredChannel;\n const tab = mergedStyle.tab!;\n\n return {\n display: 'flex',\n alignItems: 'center',\n gap: '6px',\n padding: tab.padding,\n borderRadius: tab.borderRadius,\n fontSize: tab.fontSize,\n fontWeight: tab.fontWeight,\n color: isActive ? tab.activeColor : tab.color,\n backgroundColor: isActive\n ? tab.activeBackground\n : isHovered\n ? tab.hoverBackground\n : tab.background,\n border: 'none',\n cursor: disabled ? 'not-allowed' : 'pointer',\n transition: 'all 0.15s ease',\n opacity: disabled ? 0.5 : 1,\n outline: 'none',\n };\n },\n [activeChannel, hoveredChannel, mergedStyle.tab, disabled]\n );\n\n const getIndicatorStyle = (channel: Channel): CSSProperties => {\n const color = CHANNEL_COLORS[channel];\n const isActive = channel === activeChannel;\n\n return {\n width: '8px',\n height: '8px',\n borderRadius: '50%',\n backgroundColor: color,\n boxShadow: isActive ? `0 0 6px ${color}` : 'none',\n transition: 'box-shadow 0.15s ease',\n };\n };\n\n const handleClick = useCallback(\n (channel: Channel) => {\n if (!disabled) {\n onChange(channel);\n }\n },\n [disabled, onChange]\n );\n\n return (\n <div style={containerStyle} role=\"tablist\">\n {CHANNELS.map((channel) => (\n <button\n key={channel}\n role=\"tab\"\n aria-selected={channel === activeChannel}\n aria-disabled={disabled}\n style={getTabStyle(channel)}\n onClick={() => handleClick(channel)}\n onMouseEnter={() => setHoveredChannel(channel)}\n onMouseLeave={() => setHoveredChannel(null)}\n >\n <span style={getIndicatorStyle(channel)} />\n <span>{CHANNEL_INFO[channel].label}</span>\n </button>\n ))}\n </div>\n );\n});\n","import { useState, useCallback, useMemo } from 'react';\nimport {\n CurvePoint,\n ChannelPoints,\n Channel,\n LUTData,\n} from '../types';\nimport {\n getDefaultChannelPoints,\n sortPoints,\n generateLUT,\n clamp,\n} from '../utils/curve';\nimport { MIN_POINT_DISTANCE } from '../utils/constants';\n\ninterface UseCurvePointsOptions {\n defaultPoints?: Partial<ChannelPoints>;\n controlledPoints?: Partial<ChannelPoints>;\n interpolation?: 'monotone' | 'catmullRom';\n onChange?: (points: ChannelPoints, lut: LUTData) => void;\n}\n\ninterface UseCurvePointsReturn {\n points: ChannelPoints;\n lut: LUTData;\n addPoint: (channel: Channel, point: CurvePoint) => void;\n removePoint: (channel: Channel, index: number) => void;\n updatePoint: (channel: Channel, index: number, point: CurvePoint) => void;\n resetChannel: (channel: Channel) => void;\n resetAll: () => void;\n setChannelPoints: (channel: Channel, points: CurvePoint[]) => void;\n setAllPoints: (points: Partial<ChannelPoints>) => void;\n}\n\nexport function useCurvePoints(\n options: UseCurvePointsOptions = {}\n): UseCurvePointsReturn {\n const {\n defaultPoints,\n controlledPoints,\n interpolation = 'monotone',\n onChange,\n } = options;\n\n // Merge default points with provided defaults\n const initialPoints = useMemo(() => {\n const defaults = getDefaultChannelPoints();\n if (defaultPoints) {\n return {\n master: defaultPoints.master || defaults.master,\n red: defaultPoints.red || defaults.red,\n green: defaultPoints.green || defaults.green,\n blue: defaultPoints.blue || defaults.blue,\n };\n }\n return defaults;\n }, [defaultPoints]);\n\n const [internalPoints, setInternalPoints] =\n useState<ChannelPoints>(initialPoints);\n\n // Use controlled points if provided, otherwise internal state\n const points = useMemo(() => {\n if (controlledPoints) {\n const defaults = getDefaultChannelPoints();\n return {\n master: controlledPoints.master || defaults.master,\n red: controlledPoints.red || defaults.red,\n green: controlledPoints.green || defaults.green,\n blue: controlledPoints.blue || defaults.blue,\n };\n }\n return internalPoints;\n }, [controlledPoints, internalPoints]);\n\n // Generate LUT from points\n const lut = useMemo(() => {\n return generateLUT(points, interpolation);\n }, [points, interpolation]);\n\n // Helper to update points and trigger onChange\n const updatePoints = useCallback(\n (newPoints: ChannelPoints) => {\n if (!controlledPoints) {\n setInternalPoints(newPoints);\n }\n if (onChange) {\n const newLut = generateLUT(newPoints, interpolation);\n onChange(newPoints, newLut);\n }\n },\n [controlledPoints, onChange, interpolation]\n );\n\n // Add a new point to a channel\n const addPoint = useCallback(\n (channel: Channel, point: CurvePoint) => {\n const channelPoints = points[channel];\n\n // Check if point is too close to existing points\n const sorted = sortPoints(channelPoints);\n for (const p of sorted) {\n if (Math.abs(p.x - point.x) < MIN_POINT_DISTANCE) {\n return; // Don't add if too close\n }\n }\n\n const newChannelPoints = sortPoints([...channelPoints, point]);\n const newPoints = {\n ...points,\n [channel]: newChannelPoints,\n };\n\n updatePoints(newPoints);\n },\n [points, updatePoints]\n );\n\n // Remove a point from a channel (except first and last)\n const removePoint = useCallback(\n (channel: Channel, index: number) => {\n const channelPoints = points[channel];\n\n // Can't remove if only 2 points left\n if (channelPoints.length <= 2) return;\n\n // Sort to find actual index\n const sorted = sortPoints(channelPoints);\n\n // Can't remove first or last point\n if (index === 0 || index === sorted.length - 1) return;\n\n const newChannelPoints = sorted.filter((_, i) => i !== index);\n const newPoints = {\n ...points,\n [channel]: newChannelPoints,\n };\n\n updatePoints(newPoints);\n },\n [points, updatePoints]\n );\n\n // Update a point's position\n const updatePoint = useCallback(\n (channel: Channel, index: number, newPoint: CurvePoint) => {\n const channelPoints = sortPoints(points[channel]);\n const isFirst = index === 0;\n const isLast = index === channelPoints.length - 1;\n\n // Constrain x position for first and last points\n let x = newPoint.x;\n if (isFirst) {\n x = 0;\n } else if (isLast) {\n x = 255;\n } else {\n // Constrain x between adjacent points\n const prevX = channelPoints[index - 1].x + MIN_POINT_DISTANCE;\n const nextX = channelPoints[index + 1].x - MIN_POINT_DISTANCE;\n x = clamp(x, prevX, nextX);\n }\n\n // Clamp y to valid range\n const y = clamp(newPoint.y, 0, 255);\n\n const newChannelPoints = channelPoints.map((p, i) =>\n i === index ? { x, y } : p\n );\n\n const newPoints = {\n ...points,\n [channel]: newChannelPoints,\n };\n\n updatePoints(newPoints);\n },\n [points, updatePoints]\n );\n\n // Reset a single channel\n const resetChannel = useCallback(\n (channel: Channel) => {\n const defaults = getDefaultChannelPoints();\n const newPoints = {\n ...points,\n [channel]: defaults[channel],\n };\n\n updatePoints(newPoints);\n },\n [points, updatePoints]\n );\n\n // Reset all channels\n const resetAll = useCallback(() => {\n const defaults = getDefaultChannelPoints();\n updatePoints(defaults);\n }, [updatePoints]);\n\n // Set points for a specific channel\n const setChannelPoints = useCallback(\n (channel: Channel, newChannelPoints: CurvePoint[]) => {\n const newPoints = {\n ...points,\n [channel]: sortPoints(newChannelPoints),\n };\n\n updatePoints(newPoints);\n },\n [points, updatePoints]\n );\n\n // Set all points at once\n const setAllPoints = useCallback(\n (newPoints: Partial<ChannelPoints>) => {\n const defaults = getDefaultChannelPoints();\n const mergedPoints: ChannelPoints = {\n master: sortPoints(newPoints.master || defaults.master),\n red: sortPoints(newPoints.red || defaults.red),\n green: sortPoints(newPoints.green || defaults.green),\n blue: sortPoints(newPoints.blue || defaults.blue),\n };\n\n updatePoints(mergedPoints);\n },\n [updatePoints]\n );\n\n return {\n points,\n lut,\n addPoint,\n removePoint,\n updatePoint,\n resetChannel,\n resetAll,\n setChannelPoints,\n setAllPoints,\n };\n}\n","import {\n useState,\n useCallback,\n useMemo,\n forwardRef,\n useImperativeHandle,\n CSSProperties,\n} from 'react';\nimport {\n RGBCurveProps,\n RGBCurveRef,\n Channel,\n CurveChangeData,\n ChannelPoints,\n LUTData,\n} from '../types';\nimport { CurveCanvas } from './CurveCanvas';\nimport { ChannelTabs } from './ChannelTabs';\nimport { useCurvePoints } from '../hooks/useCurvePoints';\nimport { DEFAULT_WIDTH, DEFAULT_HEIGHT, DEFAULT_STYLES } from '../utils/constants';\n\nexport const RGBCurve = forwardRef<RGBCurveRef, RGBCurveProps>(\n function RGBCurve(\n {\n width = DEFAULT_WIDTH,\n height = DEFAULT_HEIGHT,\n defaultPoints,\n points: controlledPoints,\n defaultChannel = 'master',\n activeChannel: controlledChannel,\n onChange,\n onChannelChange,\n styles = {},\n showTabs = true,\n showHistogram = false,\n histogramData,\n disabled = false,\n className,\n interpolation = 'monotone',\n },\n ref\n ) {\n // Channel state\n const [internalChannel, setInternalChannel] =\n useState<Channel>(defaultChannel);\n const activeChannel = controlledChannel ?? internalChannel;\n\n // Handle onChange to wrap with channel info\n const handlePointsChange = useCallback(\n (newPoints: ChannelPoints, newLut: LUTData) => {\n if (onChange) {\n const data: CurveChangeData = {\n points: newPoints,\n lut: newLut,\n activeChannel,\n };\n onChange(data);\n }\n },\n [onChange, activeChannel]\n );\n\n // Curve points state\n const {\n points,\n lut,\n addPoint,\n removePoint,\n updatePoint,\n resetChannel,\n resetAll,\n setAllPoints,\n } = useCurvePoints({\n defaultPoints,\n controlledPoints,\n interpolation,\n onChange: handlePointsChange,\n });\n\n // Handle channel change\n const handleChannelChange = useCallback(\n (channel: Channel) => {\n if (!controlledChannel) {\n setInternalChannel(channel);\n }\n if (onChannelChange) {\n onChannelChange(channel);\n }\n },\n [controlledChannel, onChannelChange]\n );\n\n // Expose methods via ref\n useImperativeHandle(\n ref,\n () => ({\n reset: resetAll,\n resetChannel,\n getLUT: () => lut,\n getPoints: () => points,\n setPoints: setAllPoints,\n }),\n [resetAll, resetChannel, lut, points, setAllPoints]\n );\n\n // Merge styles\n const mergedStyles = useMemo(() => {\n return {\n container: { ...DEFAULT_STYLES.container, ...styles.container },\n canvasWrapper: {\n ...DEFAULT_STYLES.canvasWrapper,\n ...styles.canvasWrapper,\n },\n grid: { ...DEFAULT_STYLES.grid, ...styles.grid },\n curve: {\n master: { ...DEFAULT_STYLES.curve.master, ...styles.curve?.master },\n red: { ...DEFAULT_STYLES.curve.red, ...styles.curve?.red },\n green: { ...DEFAULT_STYLES.curve.green, ...styles.curve?.green },\n blue: { ...DEFAULT_STYLES.curve.blue, ...styles.curve?.blue },\n },\n controlPoint: {\n ...DEFAULT_STYLES.controlPoint,\n ...styles.controlPoint,\n },\n tabs: {\n ...DEFAULT_STYLES.tabs,\n ...styles.tabs,\n tab: { ...DEFAULT_STYLES.tabs.tab, ...styles.tabs?.tab },\n },\n histogram: { ...DEFAULT_STYLES.histogram, ...styles.histogram },\n };\n }, [styles]);\n\n // Get current channel's curve style\n const currentCurveStyle = mergedStyles.curve[activeChannel];\n\n const containerStyle: CSSProperties = {\n ...mergedStyles.container,\n width: 'fit-content',\n };\n\n return (\n <div style={containerStyle} className={className}>\n {showTabs && (\n <ChannelTabs\n activeChannel={activeChannel}\n onChange={handleChannelChange}\n style={mergedStyles.tabs}\n disabled={disabled}\n />\n )}\n\n <CurveCanvas\n width={width}\n height={height}\n points={points[activeChannel]}\n channel={activeChannel}\n gridStyle={mergedStyles.grid}\n curveStyle={currentCurveStyle}\n controlPointStyle={mergedStyles.controlPoint}\n histogramStyle={{\n ...mergedStyles.histogram,\n show: showHistogram,\n }}\n histogramData={histogramData}\n wrapperStyle={mergedStyles.canvasWrapper}\n disabled={disabled}\n interpolation={interpolation}\n onAddPoint={addPoint}\n onRemovePoint={removePoint}\n onUpdatePoint={updatePoint}\n />\n </div>\n );\n }\n);\n"],"names":["clamp","value","min","max","getDefaultPoints","getDefaultChannelPoints","sortPoints","points","a","b","monotoneCubicInterpolation","x","sorted","n","i","x0","x1","y0","y1","dx","dy","m0","m1","prevDx","prevDy","nextDx","nextDy","slope","alpha","beta","sum","tau","t","t2","t3","h00","h10","h01","h11","result","catmullRomInterpolation","p0","p1","p2","p3","generateChannelLUT","interpolation","lut","interpolate","generateLUT","channelPoints","applyLUT","r","g","newR","newG","newB","isPointNear","threshold","CHANNELS","CHANNEL_INFO","DEFAULT_WIDTH","DEFAULT_HEIGHT","DEFAULT_STYLES","CHANNEL_COLORS","POINT_HIT_THRESHOLD","MIN_POINT_DISTANCE","useCanvasInteraction","options","channel","width","height","disabled","onAddPoint","onRemovePoint","onUpdatePoint","activePointIndex","setActivePointIndex","useState","hoveredPointIndex","setHoveredPointIndex","isDragging","useRef","canvasToCurve","useCallback","canvasX","canvasY","curveToCanvas","curveX","curveY","findPointAtPosition","canvasPoint","getCanvasPosition","e","rect","handleMouseDown","pos","pointIndex","curvePoint","handleMouseMove","handleMouseUp","handleMouseLeave","handleDoubleClick","useEffect","handleGlobalMouseUp","CurveCanvas","memo","gridStyle","curveStyle","controlPointStyle","histogramStyle","histogramData","wrapperStyle","canvasRef","dpr","draw","canvas","ctx","drawHistogram","drawGrid","drawCurve","drawControlPoints","jsx","style","color","lineWidth","subdivisions","showDiagonal","diagonalColor","step","data","opacity","fillColor","barWidth","barHeight","shadowColor","shadowBlur","px","activeIndex","hoveredIndex","radius","fill","stroke","strokeWidth","activeFill","activeStroke","hoverScale","point","index","isActive","currentRadius","ChannelTabs","activeChannel","onChange","hoveredChannel","setHoveredChannel","mergedStyle","containerStyle","getTabStyle","isHovered","tab","getIndicatorStyle","handleClick","jsxs","useCurvePoints","defaultPoints","controlledPoints","initialPoints","useMemo","defaults","internalPoints","setInternalPoints","updatePoints","newPoints","newLut","addPoint","p","newChannelPoints","removePoint","_","updatePoint","newPoint","isFirst","isLast","prevX","nextX","y","resetChannel","resetAll","setChannelPoints","setAllPoints","mergedPoints","RGBCurve","forwardRef","defaultChannel","controlledChannel","onChannelChange","styles","showTabs","showHistogram","className","ref","internalChannel","setInternalChannel","handlePointsChange","handleChannelChange","useImperativeHandle","mergedStyles","_a","_b","_c","_d","_e","currentCurveStyle"],"mappings":"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"}
|