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/README.md
ADDED
|
@@ -0,0 +1,737 @@
|
|
|
1
|
+
# RGB Curve
|
|
2
|
+
|
|
3
|
+
A fast, lightweight RGB curve editor component for React — like the curves tool in Adobe Lightroom, Premiere Pro, and Photoshop.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+

|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- 4 channels: **Master** (RGB), **Red**, **Green**, **Blue**
|
|
12
|
+
- Smooth cubic spline interpolation
|
|
13
|
+
- Returns **control points** + **256-value LUT** for pixel processing
|
|
14
|
+
- Beautiful dark theme UI (Lightroom/Premiere Pro inspired)
|
|
15
|
+
- Fully customizable via JSON style props
|
|
16
|
+
- TypeScript support
|
|
17
|
+
- Zero dependencies (only React as peer dep)
|
|
18
|
+
- Lightweight: ~6KB gzipped
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
npm install rgb-curve
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
yarn add rgb-curve
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pnpm add rgb-curve
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
```tsx
|
|
37
|
+
import { RGBCurve } from 'rgb-curve';
|
|
38
|
+
|
|
39
|
+
function App() {
|
|
40
|
+
return (
|
|
41
|
+
<RGBCurve
|
|
42
|
+
onChange={({ points, lut, activeChannel }) => {
|
|
43
|
+
console.log('Control points:', points);
|
|
44
|
+
console.log('LUT:', lut);
|
|
45
|
+
console.log('Active channel:', activeChannel);
|
|
46
|
+
}}
|
|
47
|
+
/>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## How to Use
|
|
53
|
+
|
|
54
|
+
| Action | Description |
|
|
55
|
+
|--------|-------------|
|
|
56
|
+
| **Click** on curve | Add a new control point |
|
|
57
|
+
| **Drag** a point | Adjust the curve |
|
|
58
|
+
| **Double-click** a point | Remove the point |
|
|
59
|
+
| **Click tabs** | Switch between Master/R/G/B channels |
|
|
60
|
+
|
|
61
|
+
## Props
|
|
62
|
+
|
|
63
|
+
| Prop | Type | Default | Description |
|
|
64
|
+
|------|------|---------|-------------|
|
|
65
|
+
| `width` | `number` | `300` | Width of the curve editor in pixels |
|
|
66
|
+
| `height` | `number` | `300` | Height of the curve editor in pixels |
|
|
67
|
+
| `defaultPoints` | `Partial<ChannelPoints>` | — | Initial control points for each channel |
|
|
68
|
+
| `points` | `Partial<ChannelPoints>` | — | Controlled points (makes component controlled) |
|
|
69
|
+
| `defaultChannel` | `Channel` | `'master'` | Initial active channel |
|
|
70
|
+
| `activeChannel` | `Channel` | — | Controlled active channel |
|
|
71
|
+
| `onChange` | `(data: CurveChangeData) => void` | — | Callback when curve changes |
|
|
72
|
+
| `onChannelChange` | `(channel: Channel) => void` | — | Callback when channel changes |
|
|
73
|
+
| `styles` | `RGBCurveStyles` | — | Custom styles (see Styling section) |
|
|
74
|
+
| `showTabs` | `boolean` | `true` | Show/hide channel tabs |
|
|
75
|
+
| `showHistogram` | `boolean` | `false` | Show/hide histogram overlay |
|
|
76
|
+
| `histogramData` | `Uint8Array` | — | Histogram data (256 values) |
|
|
77
|
+
| `disabled` | `boolean` | `false` | Disable all interactions |
|
|
78
|
+
| `className` | `string` | — | CSS class for container |
|
|
79
|
+
| `interpolation` | `'monotone' \| 'catmullRom'` | `'monotone'` | Curve interpolation type |
|
|
80
|
+
|
|
81
|
+
## onChange Data
|
|
82
|
+
|
|
83
|
+
The `onChange` callback receives an object with:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
interface CurveChangeData {
|
|
87
|
+
// Control points for all channels
|
|
88
|
+
points: {
|
|
89
|
+
master: CurvePoint[];
|
|
90
|
+
red: CurvePoint[];
|
|
91
|
+
green: CurvePoint[];
|
|
92
|
+
blue: CurvePoint[];
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Look-Up Table (256 values per channel) for pixel processing
|
|
96
|
+
lut: {
|
|
97
|
+
master: Uint8Array;
|
|
98
|
+
red: Uint8Array;
|
|
99
|
+
green: Uint8Array;
|
|
100
|
+
blue: Uint8Array;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// Currently active channel
|
|
104
|
+
activeChannel: 'master' | 'red' | 'green' | 'blue';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
interface CurvePoint {
|
|
108
|
+
x: number; // 0-255
|
|
109
|
+
y: number; // 0-255
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Ref Methods
|
|
114
|
+
|
|
115
|
+
You can access component methods via ref:
|
|
116
|
+
|
|
117
|
+
```tsx
|
|
118
|
+
import { useRef } from 'react';
|
|
119
|
+
import { RGBCurve, RGBCurveRef } from 'rgb-curve';
|
|
120
|
+
|
|
121
|
+
function App() {
|
|
122
|
+
const curveRef = useRef<RGBCurveRef>(null);
|
|
123
|
+
|
|
124
|
+
return (
|
|
125
|
+
<>
|
|
126
|
+
<RGBCurve ref={curveRef} />
|
|
127
|
+
|
|
128
|
+
<button onClick={() => curveRef.current?.reset()}>
|
|
129
|
+
Reset All
|
|
130
|
+
</button>
|
|
131
|
+
|
|
132
|
+
<button onClick={() => curveRef.current?.resetChannel('red')}>
|
|
133
|
+
Reset Red
|
|
134
|
+
</button>
|
|
135
|
+
|
|
136
|
+
<button onClick={() => {
|
|
137
|
+
const lut = curveRef.current?.getLUT();
|
|
138
|
+
console.log(lut);
|
|
139
|
+
}}>
|
|
140
|
+
Get LUT
|
|
141
|
+
</button>
|
|
142
|
+
</>
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Available Methods
|
|
148
|
+
|
|
149
|
+
| Method | Description |
|
|
150
|
+
|--------|-------------|
|
|
151
|
+
| `reset()` | Reset all channels to default (diagonal line) |
|
|
152
|
+
| `resetChannel(channel)` | Reset a specific channel |
|
|
153
|
+
| `getLUT()` | Get current LUT data |
|
|
154
|
+
| `getPoints()` | Get current control points |
|
|
155
|
+
| `setPoints(points)` | Set points programmatically |
|
|
156
|
+
|
|
157
|
+
---
|
|
158
|
+
|
|
159
|
+
## Styling
|
|
160
|
+
|
|
161
|
+
The component comes with a beautiful dark theme by default. You can customize every aspect using the `styles` prop.
|
|
162
|
+
|
|
163
|
+
### Complete Styles Example
|
|
164
|
+
|
|
165
|
+
```tsx
|
|
166
|
+
<RGBCurve
|
|
167
|
+
styles={{
|
|
168
|
+
// Container wrapper
|
|
169
|
+
container: {
|
|
170
|
+
background: '#1a1a1a',
|
|
171
|
+
borderRadius: 12,
|
|
172
|
+
padding: 16,
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
// Canvas wrapper
|
|
176
|
+
canvasWrapper: {
|
|
177
|
+
borderRadius: 8,
|
|
178
|
+
overflow: 'hidden',
|
|
179
|
+
background: '#0d0d0d',
|
|
180
|
+
},
|
|
181
|
+
|
|
182
|
+
// Grid lines
|
|
183
|
+
grid: {
|
|
184
|
+
color: '#2a2a2a',
|
|
185
|
+
lineWidth: 1,
|
|
186
|
+
subdivisions: 4,
|
|
187
|
+
showDiagonal: true,
|
|
188
|
+
diagonalColor: '#333333',
|
|
189
|
+
},
|
|
190
|
+
|
|
191
|
+
// Curve lines (per channel)
|
|
192
|
+
curve: {
|
|
193
|
+
master: {
|
|
194
|
+
color: '#e0e0e0',
|
|
195
|
+
width: 2,
|
|
196
|
+
shadowColor: 'rgba(255, 255, 255, 0.3)',
|
|
197
|
+
shadowBlur: 4,
|
|
198
|
+
},
|
|
199
|
+
red: {
|
|
200
|
+
color: '#ff6b6b',
|
|
201
|
+
width: 2,
|
|
202
|
+
shadowColor: 'rgba(255, 107, 107, 0.4)',
|
|
203
|
+
shadowBlur: 4,
|
|
204
|
+
},
|
|
205
|
+
green: {
|
|
206
|
+
color: '#51cf66',
|
|
207
|
+
width: 2,
|
|
208
|
+
shadowColor: 'rgba(81, 207, 102, 0.4)',
|
|
209
|
+
shadowBlur: 4,
|
|
210
|
+
},
|
|
211
|
+
blue: {
|
|
212
|
+
color: '#339af0',
|
|
213
|
+
width: 2,
|
|
214
|
+
shadowColor: 'rgba(51, 154, 240, 0.4)',
|
|
215
|
+
shadowBlur: 4,
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
// Control points
|
|
220
|
+
controlPoint: {
|
|
221
|
+
radius: 6,
|
|
222
|
+
fill: '#ffffff',
|
|
223
|
+
stroke: '#000000',
|
|
224
|
+
strokeWidth: 2,
|
|
225
|
+
activeFill: '#ffd43b',
|
|
226
|
+
activeStroke: '#000000',
|
|
227
|
+
hoverScale: 1.2,
|
|
228
|
+
},
|
|
229
|
+
|
|
230
|
+
// Channel tabs
|
|
231
|
+
tabs: {
|
|
232
|
+
background: '#252525',
|
|
233
|
+
borderRadius: 8,
|
|
234
|
+
gap: 4,
|
|
235
|
+
tab: {
|
|
236
|
+
padding: '8px 16px',
|
|
237
|
+
borderRadius: 6,
|
|
238
|
+
fontSize: 13,
|
|
239
|
+
fontWeight: 500,
|
|
240
|
+
color: '#808080',
|
|
241
|
+
background: 'transparent',
|
|
242
|
+
hoverBackground: '#333333',
|
|
243
|
+
activeColor: '#ffffff',
|
|
244
|
+
activeBackground: '#404040',
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
|
|
248
|
+
// Histogram (if enabled)
|
|
249
|
+
histogram: {
|
|
250
|
+
show: true,
|
|
251
|
+
opacity: 0.3,
|
|
252
|
+
fillColor: '#666666',
|
|
253
|
+
},
|
|
254
|
+
}}
|
|
255
|
+
/>
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Style Reference
|
|
261
|
+
|
|
262
|
+
### Container Style
|
|
263
|
+
|
|
264
|
+
```ts
|
|
265
|
+
container: CSSProperties
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
Standard React CSS properties for the outer container.
|
|
269
|
+
|
|
270
|
+
```tsx
|
|
271
|
+
styles={{
|
|
272
|
+
container: {
|
|
273
|
+
background: 'linear-gradient(135deg, #1a1a2e, #16213e)',
|
|
274
|
+
borderRadius: 16,
|
|
275
|
+
padding: 20,
|
|
276
|
+
boxShadow: '0 10px 40px rgba(0,0,0,0.5)',
|
|
277
|
+
}
|
|
278
|
+
}}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
### Canvas Wrapper Style
|
|
282
|
+
|
|
283
|
+
```ts
|
|
284
|
+
canvasWrapper: CSSProperties
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Standard React CSS properties for the canvas container.
|
|
288
|
+
|
|
289
|
+
```tsx
|
|
290
|
+
styles={{
|
|
291
|
+
canvasWrapper: {
|
|
292
|
+
borderRadius: 12,
|
|
293
|
+
border: '1px solid #333',
|
|
294
|
+
overflow: 'hidden',
|
|
295
|
+
}
|
|
296
|
+
}}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
### Grid Style
|
|
300
|
+
|
|
301
|
+
```ts
|
|
302
|
+
interface GridStyle {
|
|
303
|
+
color?: string; // Grid line color
|
|
304
|
+
lineWidth?: number; // Grid line width
|
|
305
|
+
subdivisions?: number; // Number of grid divisions (default: 4)
|
|
306
|
+
showDiagonal?: boolean; // Show diagonal baseline
|
|
307
|
+
diagonalColor?: string; // Diagonal line color
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
```tsx
|
|
312
|
+
styles={{
|
|
313
|
+
grid: {
|
|
314
|
+
color: '#333333',
|
|
315
|
+
lineWidth: 1,
|
|
316
|
+
subdivisions: 4,
|
|
317
|
+
showDiagonal: true,
|
|
318
|
+
diagonalColor: '#444444',
|
|
319
|
+
}
|
|
320
|
+
}}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
### Curve Style
|
|
324
|
+
|
|
325
|
+
```ts
|
|
326
|
+
interface CurveLineStyle {
|
|
327
|
+
color?: string; // Curve line color
|
|
328
|
+
width?: number; // Curve line width
|
|
329
|
+
shadowColor?: string; // Glow effect color
|
|
330
|
+
shadowBlur?: number; // Glow blur radius
|
|
331
|
+
}
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
```tsx
|
|
335
|
+
styles={{
|
|
336
|
+
curve: {
|
|
337
|
+
master: { color: '#ffffff', width: 2 },
|
|
338
|
+
red: { color: '#ff0000', width: 2, shadowColor: 'rgba(255,0,0,0.5)', shadowBlur: 8 },
|
|
339
|
+
green: { color: '#00ff00', width: 2 },
|
|
340
|
+
blue: { color: '#0088ff', width: 3 },
|
|
341
|
+
}
|
|
342
|
+
}}
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### Control Point Style
|
|
346
|
+
|
|
347
|
+
```ts
|
|
348
|
+
interface ControlPointStyle {
|
|
349
|
+
radius?: number; // Point radius
|
|
350
|
+
fill?: string; // Point fill color
|
|
351
|
+
stroke?: string; // Point border color
|
|
352
|
+
strokeWidth?: number; // Point border width
|
|
353
|
+
activeFill?: string; // Fill when dragging
|
|
354
|
+
activeStroke?: string; // Border when dragging
|
|
355
|
+
hoverScale?: number; // Scale on hover (1.2 = 120%)
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
```tsx
|
|
360
|
+
styles={{
|
|
361
|
+
controlPoint: {
|
|
362
|
+
radius: 8,
|
|
363
|
+
fill: '#ffffff',
|
|
364
|
+
stroke: '#000000',
|
|
365
|
+
strokeWidth: 2,
|
|
366
|
+
activeFill: '#ffcc00',
|
|
367
|
+
activeStroke: '#000000',
|
|
368
|
+
hoverScale: 1.3,
|
|
369
|
+
}
|
|
370
|
+
}}
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Tabs Style
|
|
374
|
+
|
|
375
|
+
```ts
|
|
376
|
+
interface TabsStyle {
|
|
377
|
+
background?: string; // Tabs container background
|
|
378
|
+
borderRadius?: number; // Tabs container border radius
|
|
379
|
+
gap?: number; // Gap between tabs
|
|
380
|
+
tab?: {
|
|
381
|
+
padding?: string;
|
|
382
|
+
borderRadius?: number;
|
|
383
|
+
fontSize?: number;
|
|
384
|
+
fontWeight?: number | string;
|
|
385
|
+
color?: string; // Inactive tab text color
|
|
386
|
+
background?: string; // Inactive tab background
|
|
387
|
+
hoverBackground?: string; // Hover background
|
|
388
|
+
activeColor?: string; // Active tab text color
|
|
389
|
+
activeBackground?: string; // Active tab background
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
```tsx
|
|
395
|
+
styles={{
|
|
396
|
+
tabs: {
|
|
397
|
+
background: '#1a1a1a',
|
|
398
|
+
borderRadius: 10,
|
|
399
|
+
gap: 8,
|
|
400
|
+
tab: {
|
|
401
|
+
padding: '10px 20px',
|
|
402
|
+
borderRadius: 8,
|
|
403
|
+
fontSize: 14,
|
|
404
|
+
fontWeight: 600,
|
|
405
|
+
color: '#666666',
|
|
406
|
+
background: 'transparent',
|
|
407
|
+
hoverBackground: '#2a2a2a',
|
|
408
|
+
activeColor: '#ffffff',
|
|
409
|
+
activeBackground: '#3a3a3a',
|
|
410
|
+
},
|
|
411
|
+
}
|
|
412
|
+
}}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### Histogram Style
|
|
416
|
+
|
|
417
|
+
```ts
|
|
418
|
+
interface HistogramStyle {
|
|
419
|
+
show?: boolean; // Show/hide histogram
|
|
420
|
+
opacity?: number; // Histogram opacity (0-1)
|
|
421
|
+
fillColor?: string; // Histogram bar color
|
|
422
|
+
}
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
```tsx
|
|
426
|
+
<RGBCurve
|
|
427
|
+
showHistogram={true}
|
|
428
|
+
histogramData={myHistogramData} // Uint8Array of 256 values
|
|
429
|
+
styles={{
|
|
430
|
+
histogram: {
|
|
431
|
+
opacity: 0.4,
|
|
432
|
+
fillColor: '#888888',
|
|
433
|
+
}
|
|
434
|
+
}}
|
|
435
|
+
/>
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
---
|
|
439
|
+
|
|
440
|
+
## Theme Examples
|
|
441
|
+
|
|
442
|
+
### Light Theme
|
|
443
|
+
|
|
444
|
+
```tsx
|
|
445
|
+
<RGBCurve
|
|
446
|
+
styles={{
|
|
447
|
+
container: {
|
|
448
|
+
background: '#f5f5f5',
|
|
449
|
+
borderRadius: 12,
|
|
450
|
+
padding: 16,
|
|
451
|
+
},
|
|
452
|
+
canvasWrapper: {
|
|
453
|
+
background: '#ffffff',
|
|
454
|
+
borderRadius: 8,
|
|
455
|
+
border: '1px solid #e0e0e0',
|
|
456
|
+
},
|
|
457
|
+
grid: {
|
|
458
|
+
color: '#e0e0e0',
|
|
459
|
+
diagonalColor: '#d0d0d0',
|
|
460
|
+
},
|
|
461
|
+
curve: {
|
|
462
|
+
master: { color: '#333333', width: 2 },
|
|
463
|
+
red: { color: '#e53935', width: 2 },
|
|
464
|
+
green: { color: '#43a047', width: 2 },
|
|
465
|
+
blue: { color: '#1e88e5', width: 2 },
|
|
466
|
+
},
|
|
467
|
+
controlPoint: {
|
|
468
|
+
fill: '#ffffff',
|
|
469
|
+
stroke: '#333333',
|
|
470
|
+
activeFill: '#ffca28',
|
|
471
|
+
},
|
|
472
|
+
tabs: {
|
|
473
|
+
background: '#e0e0e0',
|
|
474
|
+
tab: {
|
|
475
|
+
color: '#666666',
|
|
476
|
+
activeColor: '#000000',
|
|
477
|
+
activeBackground: '#ffffff',
|
|
478
|
+
hoverBackground: '#eeeeee',
|
|
479
|
+
},
|
|
480
|
+
},
|
|
481
|
+
}}
|
|
482
|
+
/>
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
### Neon Theme
|
|
486
|
+
|
|
487
|
+
```tsx
|
|
488
|
+
<RGBCurve
|
|
489
|
+
styles={{
|
|
490
|
+
container: {
|
|
491
|
+
background: '#0a0a0a',
|
|
492
|
+
borderRadius: 16,
|
|
493
|
+
padding: 20,
|
|
494
|
+
border: '1px solid #333',
|
|
495
|
+
},
|
|
496
|
+
canvasWrapper: {
|
|
497
|
+
background: '#000000',
|
|
498
|
+
borderRadius: 12,
|
|
499
|
+
},
|
|
500
|
+
grid: {
|
|
501
|
+
color: '#1a1a1a',
|
|
502
|
+
diagonalColor: '#2a2a2a',
|
|
503
|
+
},
|
|
504
|
+
curve: {
|
|
505
|
+
master: {
|
|
506
|
+
color: '#00ffff',
|
|
507
|
+
width: 2,
|
|
508
|
+
shadowColor: '#00ffff',
|
|
509
|
+
shadowBlur: 10
|
|
510
|
+
},
|
|
511
|
+
red: {
|
|
512
|
+
color: '#ff0066',
|
|
513
|
+
width: 2,
|
|
514
|
+
shadowColor: '#ff0066',
|
|
515
|
+
shadowBlur: 10
|
|
516
|
+
},
|
|
517
|
+
green: {
|
|
518
|
+
color: '#00ff66',
|
|
519
|
+
width: 2,
|
|
520
|
+
shadowColor: '#00ff66',
|
|
521
|
+
shadowBlur: 10
|
|
522
|
+
},
|
|
523
|
+
blue: {
|
|
524
|
+
color: '#0066ff',
|
|
525
|
+
width: 2,
|
|
526
|
+
shadowColor: '#0066ff',
|
|
527
|
+
shadowBlur: 10
|
|
528
|
+
},
|
|
529
|
+
},
|
|
530
|
+
controlPoint: {
|
|
531
|
+
radius: 5,
|
|
532
|
+
fill: '#ffffff',
|
|
533
|
+
stroke: '#00ffff',
|
|
534
|
+
strokeWidth: 2,
|
|
535
|
+
activeFill: '#ff00ff',
|
|
536
|
+
hoverScale: 1.4,
|
|
537
|
+
},
|
|
538
|
+
tabs: {
|
|
539
|
+
background: '#111111',
|
|
540
|
+
tab: {
|
|
541
|
+
color: '#666666',
|
|
542
|
+
activeColor: '#00ffff',
|
|
543
|
+
activeBackground: '#1a1a1a',
|
|
544
|
+
hoverBackground: '#1a1a1a',
|
|
545
|
+
},
|
|
546
|
+
},
|
|
547
|
+
}}
|
|
548
|
+
/>
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
### Minimal Theme
|
|
552
|
+
|
|
553
|
+
```tsx
|
|
554
|
+
<RGBCurve
|
|
555
|
+
styles={{
|
|
556
|
+
container: {
|
|
557
|
+
background: 'transparent',
|
|
558
|
+
padding: 0,
|
|
559
|
+
},
|
|
560
|
+
canvasWrapper: {
|
|
561
|
+
background: '#1a1a1a',
|
|
562
|
+
borderRadius: 4,
|
|
563
|
+
},
|
|
564
|
+
grid: {
|
|
565
|
+
color: '#252525',
|
|
566
|
+
showDiagonal: false,
|
|
567
|
+
subdivisions: 2,
|
|
568
|
+
},
|
|
569
|
+
curve: {
|
|
570
|
+
master: { color: '#888', width: 1.5, shadowBlur: 0 },
|
|
571
|
+
red: { color: '#f66', width: 1.5, shadowBlur: 0 },
|
|
572
|
+
green: { color: '#6f6', width: 1.5, shadowBlur: 0 },
|
|
573
|
+
blue: { color: '#66f', width: 1.5, shadowBlur: 0 },
|
|
574
|
+
},
|
|
575
|
+
controlPoint: {
|
|
576
|
+
radius: 4,
|
|
577
|
+
fill: '#fff',
|
|
578
|
+
stroke: 'transparent',
|
|
579
|
+
strokeWidth: 0,
|
|
580
|
+
hoverScale: 1.5,
|
|
581
|
+
},
|
|
582
|
+
tabs: {
|
|
583
|
+
background: 'transparent',
|
|
584
|
+
gap: 8,
|
|
585
|
+
tab: {
|
|
586
|
+
padding: '6px 12px',
|
|
587
|
+
background: 'transparent',
|
|
588
|
+
hoverBackground: '#252525',
|
|
589
|
+
activeBackground: '#333',
|
|
590
|
+
},
|
|
591
|
+
},
|
|
592
|
+
}}
|
|
593
|
+
/>
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
---
|
|
597
|
+
|
|
598
|
+
## Applying LUT to Images
|
|
599
|
+
|
|
600
|
+
The LUT (Look-Up Table) returned by `onChange` can be used for fast pixel processing:
|
|
601
|
+
|
|
602
|
+
```tsx
|
|
603
|
+
import { RGBCurve, applyLUT, LUTData } from 'rgb-curve';
|
|
604
|
+
|
|
605
|
+
function ImageEditor() {
|
|
606
|
+
const canvasRef = useRef<HTMLCanvasElement>(null);
|
|
607
|
+
const originalImageData = useRef<ImageData | null>(null);
|
|
608
|
+
|
|
609
|
+
const handleCurveChange = ({ lut }: { lut: LUTData }) => {
|
|
610
|
+
const canvas = canvasRef.current;
|
|
611
|
+
if (!canvas || !originalImageData.current) return;
|
|
612
|
+
|
|
613
|
+
const ctx = canvas.getContext('2d');
|
|
614
|
+
if (!ctx) return;
|
|
615
|
+
|
|
616
|
+
// Clone original data
|
|
617
|
+
const imageData = new ImageData(
|
|
618
|
+
new Uint8ClampedArray(originalImageData.current.data),
|
|
619
|
+
originalImageData.current.width,
|
|
620
|
+
originalImageData.current.height
|
|
621
|
+
);
|
|
622
|
+
|
|
623
|
+
// Apply LUT to each pixel
|
|
624
|
+
const data = imageData.data;
|
|
625
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
626
|
+
const [r, g, b] = applyLUT(data[i], data[i + 1], data[i + 2], lut);
|
|
627
|
+
data[i] = r;
|
|
628
|
+
data[i + 1] = g;
|
|
629
|
+
data[i + 2] = b;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
ctx.putImageData(imageData, 0, 0);
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
return (
|
|
636
|
+
<div>
|
|
637
|
+
<RGBCurve onChange={handleCurveChange} />
|
|
638
|
+
<canvas ref={canvasRef} />
|
|
639
|
+
</div>
|
|
640
|
+
);
|
|
641
|
+
}
|
|
642
|
+
```
|
|
643
|
+
|
|
644
|
+
---
|
|
645
|
+
|
|
646
|
+
## Utility Exports
|
|
647
|
+
|
|
648
|
+
The package exports several utilities for advanced use cases:
|
|
649
|
+
|
|
650
|
+
```tsx
|
|
651
|
+
import {
|
|
652
|
+
// Components
|
|
653
|
+
RGBCurve,
|
|
654
|
+
CurveCanvas,
|
|
655
|
+
ChannelTabs,
|
|
656
|
+
|
|
657
|
+
// Hooks
|
|
658
|
+
useCurvePoints,
|
|
659
|
+
useCanvasInteraction,
|
|
660
|
+
|
|
661
|
+
// Utilities
|
|
662
|
+
generateLUT,
|
|
663
|
+
generateChannelLUT,
|
|
664
|
+
applyLUT,
|
|
665
|
+
getDefaultPoints,
|
|
666
|
+
getDefaultChannelPoints,
|
|
667
|
+
monotoneCubicInterpolation,
|
|
668
|
+
catmullRomInterpolation,
|
|
669
|
+
sortPoints,
|
|
670
|
+
clamp,
|
|
671
|
+
|
|
672
|
+
// Constants
|
|
673
|
+
CHANNELS,
|
|
674
|
+
CHANNEL_INFO,
|
|
675
|
+
CHANNEL_COLORS,
|
|
676
|
+
DEFAULT_STYLES,
|
|
677
|
+
DEFAULT_WIDTH,
|
|
678
|
+
DEFAULT_HEIGHT,
|
|
679
|
+
|
|
680
|
+
// Types
|
|
681
|
+
type CurvePoint,
|
|
682
|
+
type Channel,
|
|
683
|
+
type ChannelPoints,
|
|
684
|
+
type LUTData,
|
|
685
|
+
type CurveChangeData,
|
|
686
|
+
type RGBCurveProps,
|
|
687
|
+
type RGBCurveRef,
|
|
688
|
+
type RGBCurveStyles,
|
|
689
|
+
} from 'rgb-curve';
|
|
690
|
+
```
|
|
691
|
+
|
|
692
|
+
---
|
|
693
|
+
|
|
694
|
+
## TypeScript
|
|
695
|
+
|
|
696
|
+
Full TypeScript support is included. Import types as needed:
|
|
697
|
+
|
|
698
|
+
```tsx
|
|
699
|
+
import type {
|
|
700
|
+
CurvePoint,
|
|
701
|
+
Channel,
|
|
702
|
+
ChannelPoints,
|
|
703
|
+
LUTData,
|
|
704
|
+
CurveChangeData,
|
|
705
|
+
RGBCurveProps,
|
|
706
|
+
RGBCurveRef,
|
|
707
|
+
RGBCurveStyles,
|
|
708
|
+
CurveLineStyle,
|
|
709
|
+
ControlPointStyle,
|
|
710
|
+
GridStyle,
|
|
711
|
+
TabsStyle,
|
|
712
|
+
HistogramStyle,
|
|
713
|
+
} from 'rgb-curve';
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
---
|
|
717
|
+
|
|
718
|
+
## Browser Support
|
|
719
|
+
|
|
720
|
+
- Chrome (latest)
|
|
721
|
+
- Firefox (latest)
|
|
722
|
+
- Safari (latest)
|
|
723
|
+
- Edge (latest)
|
|
724
|
+
|
|
725
|
+
Requires browsers with Canvas 2D support.
|
|
726
|
+
|
|
727
|
+
---
|
|
728
|
+
|
|
729
|
+
## License
|
|
730
|
+
|
|
731
|
+
MIT
|
|
732
|
+
|
|
733
|
+
---
|
|
734
|
+
|
|
735
|
+
## Contributing
|
|
736
|
+
|
|
737
|
+
Contributions are welcome! Please open an issue or submit a PR.
|