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 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
+ ![npm bundle size](https://img.shields.io/bundlephobia/minzip/rgb-curve)
6
+ ![npm](https://img.shields.io/npm/v/rgb-curve)
7
+ ![license](https://img.shields.io/npm/l/rgb-curve)
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.