@hyperframes/core 0.7.70 → 0.7.72

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.
@@ -1,3 +1,5 @@
1
+ import { DEFAULT_MAX_CUBE_LUT_SIZE } from "./colorLuts.js";
2
+ import { COLOR_GRADING_ADJUST_KEYS, COLOR_GRADING_COLOR_SPACE, COLOR_GRADING_DETAIL_KEYS, COLOR_GRADING_EFFECT_KEYS, COLOR_GRADING_LUT_KEYS, COLOR_GRADING_TOP_LEVEL_KEYS, } from "@hyperframes/parsers/color-grading-contract";
1
3
  export const HF_COLOR_GRADING_ATTR = "data-color-grading";
2
4
  // Runtime <-> studio contract attributes. The runtime grading engine writes
3
5
  // them; studio editing/soft-reload code reads them. Single owner — never
@@ -11,7 +13,7 @@ export const COLOR_GRADING_SOURCE_HIDDEN_ATTR = "data-hf-color-grading-source-hi
11
13
  */
12
14
  export const COLOR_GRADING_AUTHORED_OPACITY_ATTR = "data-hf-authored-opacity";
13
15
  export const HF_COLOR_GRADING_CANVAS_ID_PREFIX = "__hf_color_grading_";
14
- export const HF_COLOR_GRADING_COLOR_SPACE = "rec709";
16
+ export const HF_COLOR_GRADING_COLOR_SPACE = COLOR_GRADING_COLOR_SPACE;
15
17
  const ADJUST_ZERO = {
16
18
  exposure: 0,
17
19
  contrast: 0,
@@ -24,9 +26,7 @@ const ADJUST_ZERO = {
24
26
  vibrance: 0,
25
27
  saturation: 0,
26
28
  };
27
- // Detail sub-controls keep identity-state defaults so enabling vignette/grain starts from useful
28
- // perceptual settings instead of raw mathematical zeroes.
29
- const DETAIL_ZERO = {
29
+ const DETAIL_DEFAULTS = {
30
30
  vignette: 0,
31
31
  vignetteMidpoint: 0.5,
32
32
  vignetteRoundness: 0,
@@ -35,40 +35,188 @@ const DETAIL_ZERO = {
35
35
  grainSize: 0.25,
36
36
  grainRoughness: 0.5,
37
37
  };
38
- const EFFECT_ZERO = {
38
+ const EFFECT_DEFAULTS = {
39
39
  blur: 0,
40
40
  pixelate: 0,
41
+ chromaBleed: 0,
42
+ tapeDamage: 0,
43
+ tapeTracking: 0,
44
+ tapeNoise: 1,
45
+ tapeSpeed: 0.5,
46
+ filmArtifacts: 0,
47
+ halftone: 0,
48
+ halftoneSize: 0,
49
+ twoInkPrint: 0,
50
+ twoInkPrintSize: 0,
51
+ ascii: 0,
52
+ asciiSize: 5 / 76,
53
+ asciiInvert: 0,
54
+ asciiStyle: 0,
55
+ asciiColor: 1,
56
+ asciiRotation: 0,
57
+ dither: 0,
58
+ ditherSize: 0,
59
+ bloom: 0,
60
+ bloomRadius: 8,
61
+ monoScreen: 0,
62
+ monoScreenSize: 0,
63
+ monoScreenAngle: 0,
64
+ monoScreenSpread: 0,
65
+ monoScreenShape: 0,
66
+ monoScreenInvert: 0,
67
+ scanlines: 0,
68
+ scanlineCount: 0,
69
+ scanlineSoftness: 0,
70
+ chromaticAberration: 0,
71
+ chromaticAngle: 0,
72
+ crtCurvature: 0,
73
+ digitalGlitch: 0,
74
+ digitalGlitchColorSplit: 0,
75
+ digitalGlitchLineTear: 0,
76
+ digitalGlitchPixelate: 0,
77
+ digitalGlitchBlockAmount: 0,
78
+ digitalGlitchBlockDisplacement: 0,
79
+ digitalGlitchBlockOpacity: 0,
80
+ digitalGlitchSpeed: 0,
81
+ engraving: 0,
82
+ engravingSpacing: 7 / 17,
83
+ engravingMinThickness: 0.2,
84
+ engravingMaxThickness: 3.2 / 7,
85
+ engravingAngle: 0.25,
86
+ engravingContrast: 7 / 15,
87
+ engravingSharpness: 0.59,
88
+ engravingWave: 0.2,
89
+ engravingWaveFrequency: 2 / 9,
90
+ crosshatch: 0,
91
+ crosshatchSpacing: 7 / 25,
92
+ crosshatchThickness: 0.25,
93
+ crosshatchAngle: 0.25,
94
+ crosshatchContrast: 1 / 3,
95
+ crosshatchEdges: 0.5,
96
+ crosshatchLineWeight: 0,
97
+ crosshatchWave: 0.33,
98
+ crosshatchWaveFrequency: 2 / 9,
99
+ kuwahara: 0,
100
+ kuwaharaRadius: 1 / 7,
101
+ kuwaharaSharpness: 5 / 16,
102
+ kuwaharaSaturation: 0.5,
41
103
  };
42
- export const HF_COLOR_GRADING_ADJUST_KEYS = Object.keys(ADJUST_ZERO);
43
- export const HF_COLOR_GRADING_DETAIL_KEYS = Object.keys(DETAIL_ZERO);
44
- export const HF_COLOR_GRADING_EFFECT_KEYS = Object.keys(EFFECT_ZERO);
45
- function preset(id, label, adjust = {}, details = {}) {
104
+ export const HF_COLOR_GRADING_TOP_LEVEL_KEYS = COLOR_GRADING_TOP_LEVEL_KEYS;
105
+ export const HF_COLOR_GRADING_LUT_KEYS = COLOR_GRADING_LUT_KEYS;
106
+ export const HF_COLOR_GRADING_PALETTES = [
107
+ { id: "noir", label: "Noir", group: "Classic", colors: ["#000000", "#ffffff"] },
108
+ {
109
+ id: "ink-paper",
110
+ label: "Ink & Paper",
111
+ group: "Classic",
112
+ colors: ["#1a1a2e", "#f5f5dc"],
113
+ },
114
+ {
115
+ id: "terminal",
116
+ label: "Terminal",
117
+ group: "Classic",
118
+ colors: ["#001100", "#00ff00"],
119
+ },
120
+ {
121
+ id: "amber-glow",
122
+ label: "Amber Glow",
123
+ group: "Classic",
124
+ colors: ["#1a0f00", "#ffcc00"],
125
+ },
126
+ {
127
+ id: "handheld-green",
128
+ label: "Handheld Green",
129
+ group: "Classic",
130
+ colors: ["#0f380f", "#306230", "#8bac0f", "#9bbc0f"],
131
+ },
132
+ {
133
+ id: "golden-hour",
134
+ label: "Golden Hour",
135
+ group: "Mood",
136
+ colors: ["#1a1205", "#4a3510", "#8b6914", "#d4a017", "#fff8dc"],
137
+ },
138
+ {
139
+ id: "deep-sea",
140
+ label: "Deep Sea",
141
+ group: "Mood",
142
+ colors: ["#0a1628", "#1a3a5c", "#2d6187", "#5ba4c9", "#a8dce8"],
143
+ },
144
+ {
145
+ id: "arctic-night",
146
+ label: "Arctic Night",
147
+ group: "Mood",
148
+ colors: ["#0a0a14", "#1a2a4a", "#3a5a8a", "#6a9aca", "#cae8ff"],
149
+ },
150
+ {
151
+ id: "synthwave",
152
+ label: "Synthwave",
153
+ group: "Mood",
154
+ colors: ["#120458", "#7b2cbf", "#e040fb", "#ff6ec7", "#fff59d"],
155
+ },
156
+ {
157
+ id: "vaporwave",
158
+ label: "Vaporwave",
159
+ group: "Mood",
160
+ colors: ["#1a0a2e", "#3d1a5c", "#ff71ce", "#01cdfe", "#fffb96"],
161
+ },
162
+ {
163
+ id: "forest",
164
+ label: "Forest",
165
+ group: "Mood",
166
+ colors: ["#1a2e1a", "#2d4a2d", "#4a7c4a", "#7ab37a", "#c8e6c8"],
167
+ },
168
+ {
169
+ id: "sepia",
170
+ label: "Sepia",
171
+ group: "Mono",
172
+ colors: ["#1a1610", "#3d3020", "#6b5a40", "#a89070", "#e8dcc8"],
173
+ },
174
+ {
175
+ id: "blueprint",
176
+ label: "Blueprint",
177
+ group: "Mono",
178
+ colors: ["#001830", "#003060", "#0050a0", "#0080e0", "#e0f0ff"],
179
+ },
180
+ {
181
+ id: "warm-print",
182
+ label: "Warm Print",
183
+ group: "HyperFrames",
184
+ colors: ["#17121a", "#824c50", "#e09873", "#f7ddb1"],
185
+ },
186
+ {
187
+ id: "electric-ink",
188
+ label: "Electric Ink",
189
+ group: "HyperFrames",
190
+ colors: ["#080717", "#3c185f", "#7e2278", "#d9339f", "#ff6b66", "#aafae0"],
191
+ },
192
+ ];
193
+ export const HF_COLOR_GRADING_ADJUST_KEYS = COLOR_GRADING_ADJUST_KEYS;
194
+ export const HF_COLOR_GRADING_DETAIL_KEYS = COLOR_GRADING_DETAIL_KEYS;
195
+ export const HF_COLOR_GRADING_EFFECT_KEYS = COLOR_GRADING_EFFECT_KEYS;
196
+ const VINTAGE_WASH_ADJUST = {
197
+ exposure: 0.03,
198
+ contrast: -0.12,
199
+ highlights: -0.1,
200
+ shadows: 0.16,
201
+ whites: -0.04,
202
+ blacks: 0.08,
203
+ temperature: 0.13,
204
+ vibrance: -0.08,
205
+ saturation: -0.08,
206
+ };
207
+ const VINTAGE_WASH_DETAILS = { vignette: 0.18 };
208
+ function preset(id, label, adjust = {}, details = {}, effects = {}, intensity = 1) {
46
209
  return {
47
210
  id,
48
211
  label,
212
+ intensity,
49
213
  adjust: { ...ADJUST_ZERO, ...adjust },
50
- details: { ...DETAIL_ZERO, ...details },
51
- effects: { ...EFFECT_ZERO },
214
+ details: { ...DETAIL_DEFAULTS, ...details },
215
+ effects: { ...EFFECT_DEFAULTS, ...effects },
52
216
  };
53
217
  }
54
218
  export const HF_COLOR_GRADING_PRESETS = [
55
219
  preset("neutral", "Neutral"),
56
- preset("natural-lift", "Natural Lift", {
57
- exposure: 0.04,
58
- contrast: 0.06,
59
- highlights: -0.06,
60
- shadows: 0.08,
61
- saturation: 0.05,
62
- }),
63
- preset("fresh-pop", "Fresh Pop", {
64
- exposure: 0.08,
65
- contrast: 0.12,
66
- whites: 0.06,
67
- shadows: 0.04,
68
- temperature: -0.02,
69
- vibrance: 0.08,
70
- saturation: 0.16,
71
- }),
72
220
  preset("warm-daylight", "Warm Daylight", {
73
221
  exposure: 0.06,
74
222
  contrast: 0.07,
@@ -123,19 +271,7 @@ export const HF_COLOR_GRADING_PRESETS = [
123
271
  }, {
124
272
  vignette: 0.1,
125
273
  }),
126
- preset("vintage-wash", "Vintage Wash", {
127
- exposure: 0.03,
128
- contrast: -0.12,
129
- highlights: -0.1,
130
- shadows: 0.16,
131
- whites: -0.04,
132
- blacks: 0.08,
133
- temperature: 0.13,
134
- vibrance: -0.08,
135
- saturation: -0.08,
136
- }, {
137
- vignette: 0.18,
138
- }),
274
+ preset("vintage-wash", "Vintage Wash", VINTAGE_WASH_ADJUST, VINTAGE_WASH_DETAILS),
139
275
  preset("mono-clean", "Mono Clean", {
140
276
  contrast: 0.12,
141
277
  highlights: -0.04,
@@ -152,23 +288,6 @@ export const HF_COLOR_GRADING_PRESETS = [
152
288
  }, {
153
289
  vignette: 0.08,
154
290
  }),
155
- preset("warm-clean", "Warm Clean", {
156
- exposure: 0.05,
157
- contrast: 0.08,
158
- highlights: -0.08,
159
- shadows: 0.08,
160
- temperature: 0.16,
161
- vibrance: 0.04,
162
- saturation: 0.06,
163
- }),
164
- preset("cool-clean", "Cool Clean", {
165
- contrast: 0.06,
166
- highlights: -0.06,
167
- shadows: 0.06,
168
- temperature: -0.12,
169
- tint: 0.04,
170
- saturation: 0.04,
171
- }),
172
291
  preset("soft-boost", "Soft Boost", {
173
292
  exposure: 0.06,
174
293
  contrast: -0.04,
@@ -193,6 +312,42 @@ export const HF_COLOR_GRADING_PRESETS = [
193
312
  blacks: -0.12,
194
313
  saturation: 0.06,
195
314
  }),
315
+ preset("creator-camcorder", "Creator Camcorder", {
316
+ contrast: 0.08,
317
+ highlights: -0.05,
318
+ shadows: 0.02,
319
+ whites: 0.03,
320
+ blacks: -0.04,
321
+ temperature: -0.03,
322
+ tint: -0.015,
323
+ vibrance: -0.03,
324
+ saturation: -0.06,
325
+ }, { vignette: 0.06, grain: 0.08, grainSize: 0.18, grainRoughness: 0.58 }, { chromaBleed: 0.55 }, 0.72),
326
+ preset("vhs-playback", "VHS Playback", { contrast: -0.04, saturation: -0.08 }, { grain: 0.16, grainSize: 0.12, grainRoughness: 0.72 }, {
327
+ tapeDamage: 0.82,
328
+ tapeTracking: 0.85,
329
+ tapeNoise: 0.3,
330
+ tapeSpeed: 0.5,
331
+ chromaBleed: 0.5,
332
+ chromaticAberration: 0.18,
333
+ scanlines: 0.35,
334
+ scanlineCount: 0.17,
335
+ scanlineSoftness: 1,
336
+ digitalGlitch: 0.32,
337
+ digitalGlitchLineTear: 0.08,
338
+ digitalGlitchSpeed: 0.5,
339
+ }),
340
+ preset("home-movie-8mm", "8mm Home Movie", VINTAGE_WASH_ADJUST, {
341
+ ...VINTAGE_WASH_DETAILS,
342
+ vignette: 0.28,
343
+ vignetteMidpoint: 0.54,
344
+ vignetteFeather: 0.72,
345
+ grain: 0.34,
346
+ grainSize: 0.18,
347
+ grainRoughness: 0.72,
348
+ }, { filmArtifacts: 0.62 }, 0.72),
349
+ preset("editorial-halftone", "Editorial Halftone", { contrast: 0.04, saturation: 0.04 }, {}, { halftone: 0.94, halftoneSize: 0.36 }),
350
+ preset("two-ink-print", "Two-Ink Print", { contrast: 0.08, highlights: -0.06, shadows: 0.04 }, {}, { twoInkPrint: 1, twoInkPrintSize: 0.42 }),
196
351
  ];
197
352
  const PRESETS_BY_ID = new Map(HF_COLOR_GRADING_PRESETS.map((preset) => [preset.id, preset]));
198
353
  const VARIABLE_REF_RE = /^\$(?:\{([A-Za-z0-9_.:-]+)\}|([A-Za-z0-9_.:-]+))$/;
@@ -217,10 +372,308 @@ const DETAIL_LIMITS = {
217
372
  grainSize: { min: 0, max: 1 },
218
373
  grainRoughness: { min: 0, max: 1 },
219
374
  };
220
- const EFFECT_LIMITS = {
221
- blur: { min: 0, max: 1 },
222
- pixelate: { min: 0, max: 1 },
375
+ const UNIT_LIMIT = { min: 0, max: 1 };
376
+ const EFFECT_LIMIT_OVERRIDES = {
377
+ asciiStyle: { min: 0, max: 7 },
378
+ bloom: { min: 0, max: 3 },
379
+ bloomRadius: { min: 1, max: 100 },
380
+ monoScreenShape: { min: 0, max: 4 },
223
381
  };
382
+ export const HF_COLOR_GRADING_ACTIVE_EFFECT_KEYS = [
383
+ "blur",
384
+ "pixelate",
385
+ "chromaBleed",
386
+ "tapeDamage",
387
+ "filmArtifacts",
388
+ "halftone",
389
+ "twoInkPrint",
390
+ "ascii",
391
+ "dither",
392
+ "bloom",
393
+ "monoScreen",
394
+ "scanlines",
395
+ "chromaticAberration",
396
+ "crtCurvature",
397
+ "digitalGlitch",
398
+ "engraving",
399
+ "crosshatch",
400
+ "kuwahara",
401
+ ];
402
+ const EFFECT_FAMILIES = [
403
+ {
404
+ id: "essentials",
405
+ label: "Essentials",
406
+ description: "Common optical, focus, and privacy-oriented primitives.",
407
+ },
408
+ {
409
+ id: "retro-glitch",
410
+ label: "Retro & Glitch",
411
+ description: "Tape, film, CRT, channel separation, and digital disruption.",
412
+ },
413
+ {
414
+ id: "print",
415
+ label: "Print",
416
+ description: "Halftone, limited-ink, dither, and screen-print rendering.",
417
+ },
418
+ {
419
+ id: "art",
420
+ label: "Art",
421
+ description: "ASCII, engraved, crosshatched, and painterly rendering.",
422
+ },
423
+ ];
424
+ const EFFECT_METADATA = {
425
+ blur: {
426
+ family: "essentials",
427
+ label: "Blur",
428
+ description: "Defocus or soften a full media layer.",
429
+ },
430
+ pixelate: {
431
+ family: "essentials",
432
+ label: "Pixelate",
433
+ description: "Turn a media layer into a block mosaic.",
434
+ },
435
+ bloom: {
436
+ family: "essentials",
437
+ label: "Bloom",
438
+ description: "Add thresholded glow around bright image regions.",
439
+ },
440
+ chromaBleed: {
441
+ family: "retro-glitch",
442
+ label: "Chroma Softening",
443
+ description: "Smear color like low-bandwidth video.",
444
+ },
445
+ tapeDamage: {
446
+ family: "retro-glitch",
447
+ label: "Tape Damage",
448
+ description: "Add moving tracking errors and tape noise.",
449
+ },
450
+ filmArtifacts: {
451
+ family: "retro-glitch",
452
+ label: "Film Artifacts",
453
+ description: "Add deterministic dust and film wear.",
454
+ },
455
+ scanlines: {
456
+ family: "retro-glitch",
457
+ label: "Scanlines",
458
+ description: "Overlay configurable horizontal display lines.",
459
+ },
460
+ crtCurvature: {
461
+ family: "retro-glitch",
462
+ label: "CRT Curvature",
463
+ description: "Warp media toward curved CRT geometry.",
464
+ },
465
+ chromaticAberration: {
466
+ family: "retro-glitch",
467
+ label: "Channel Separation",
468
+ description: "Offset color channels by angle.",
469
+ },
470
+ digitalGlitch: {
471
+ family: "retro-glitch",
472
+ label: "Digital Glitch",
473
+ description: "Compose tears, blocks, and color splits.",
474
+ },
475
+ halftone: {
476
+ family: "print",
477
+ label: "Halftone",
478
+ description: "Render source color through a print-dot raster.",
479
+ },
480
+ twoInkPrint: {
481
+ family: "print",
482
+ label: "Two-Ink Print",
483
+ description: "Reduce media to a two-ink print treatment.",
484
+ },
485
+ dither: {
486
+ family: "print",
487
+ label: "Ordered Dither",
488
+ description: "Quantize media into an ordered limited palette.",
489
+ },
490
+ monoScreen: {
491
+ family: "print",
492
+ label: "Mono Screen",
493
+ description: "Build monochrome dot, shape, or line artwork.",
494
+ },
495
+ ascii: {
496
+ family: "art",
497
+ label: "ASCII",
498
+ description: "Render media as configurable procedural glyph cells.",
499
+ },
500
+ engraving: {
501
+ family: "art",
502
+ label: "Engraving",
503
+ description: "Translate luminance into directional engraved lines.",
504
+ },
505
+ crosshatch: {
506
+ family: "art",
507
+ label: "Crosshatch",
508
+ description: "Translate media into layered hand-hatched lines.",
509
+ },
510
+ kuwahara: {
511
+ family: "art",
512
+ label: "Kuwahara Paint",
513
+ description: "Apply edge-preserving painterly smoothing.",
514
+ },
515
+ };
516
+ export const HF_COLOR_GRADING_EFFECT_PRESETS = HF_COLOR_GRADING_PRESETS.filter((preset) => HF_COLOR_GRADING_ACTIVE_EFFECT_KEYS.some((key) => preset.effects[key] > 0.0001));
517
+ export const HF_COLOR_GRADING_GRADE_PRESETS = HF_COLOR_GRADING_PRESETS.filter((preset) => !HF_COLOR_GRADING_EFFECT_PRESETS.includes(preset));
518
+ /** Useful one-click values. Sub-controls not listed here keep their normalized defaults. */
519
+ export const HF_COLOR_GRADING_EFFECT_APPLY_DEFAULTS = {
520
+ blur: { blur: 0.45 },
521
+ pixelate: { pixelate: 0.55 },
522
+ bloom: { bloom: 0.55, bloomRadius: 8 },
523
+ chromaBleed: { chromaBleed: 0.55 },
524
+ tapeDamage: {
525
+ tapeDamage: 0.65,
526
+ tapeTracking: 0.55,
527
+ tapeNoise: 0.25,
528
+ tapeSpeed: 0.5,
529
+ },
530
+ filmArtifacts: { filmArtifacts: 0.55 },
531
+ scanlines: { scanlines: 0.35, scanlineCount: 0.17, scanlineSoftness: 1 },
532
+ chromaticAberration: { chromaticAberration: 0.15, chromaticAngle: 0 },
533
+ crtCurvature: { crtCurvature: 0.2 },
534
+ digitalGlitch: {
535
+ digitalGlitch: 0.55,
536
+ digitalGlitchColorSplit: 0.25,
537
+ digitalGlitchLineTear: 0.25,
538
+ digitalGlitchPixelate: 0.15,
539
+ digitalGlitchBlockAmount: 0.5,
540
+ digitalGlitchBlockDisplacement: 0.25,
541
+ digitalGlitchBlockOpacity: 0,
542
+ digitalGlitchSpeed: 0.5,
543
+ },
544
+ halftone: { halftone: 0.94, halftoneSize: 0.36 },
545
+ twoInkPrint: { twoInkPrint: 1, twoInkPrintSize: 0.42 },
546
+ ascii: {
547
+ ascii: 1,
548
+ asciiSize: 5 / 76,
549
+ asciiInvert: 0,
550
+ asciiStyle: 0,
551
+ asciiColor: 1,
552
+ asciiRotation: 0,
553
+ },
554
+ dither: { dither: 1, ditherSize: 0.5 },
555
+ monoScreen: {
556
+ monoScreen: 1,
557
+ monoScreenSize: 0.35,
558
+ monoScreenAngle: 0.25,
559
+ monoScreenSpread: 0.3,
560
+ monoScreenShape: 0,
561
+ monoScreenInvert: 0,
562
+ },
563
+ engraving: {
564
+ engraving: 1,
565
+ engravingSpacing: 7 / 17,
566
+ engravingMinThickness: 0.2,
567
+ engravingMaxThickness: 3.2 / 7,
568
+ engravingAngle: 0.25,
569
+ engravingContrast: 7 / 15,
570
+ engravingSharpness: 0.59,
571
+ engravingWave: 0.2,
572
+ engravingWaveFrequency: 2 / 9,
573
+ },
574
+ crosshatch: {
575
+ crosshatch: 1,
576
+ crosshatchSpacing: 7 / 25,
577
+ crosshatchThickness: 0.25,
578
+ crosshatchAngle: 0.25,
579
+ crosshatchContrast: 1 / 3,
580
+ crosshatchEdges: 0.5,
581
+ crosshatchLineWeight: 0,
582
+ crosshatchWave: 0.33,
583
+ crosshatchWaveFrequency: 2 / 9,
584
+ },
585
+ kuwahara: {
586
+ kuwahara: 1,
587
+ kuwaharaRadius: 1 / 7,
588
+ kuwaharaSharpness: 5 / 16,
589
+ kuwaharaSaturation: 0.5,
590
+ },
591
+ };
592
+ export const HF_COLOR_GRADING_ANIMATABLE_PROPERTIES = [
593
+ { path: "intensity", name: "--hf-color-grading-intensity", min: 0, max: 1 },
594
+ { path: "lut.intensity", name: "--hf-color-grading-lut-intensity", min: 0, max: 1 },
595
+ { path: "adjust.exposure", name: "--hf-color-grading-exposure", min: -2, max: 2 },
596
+ { path: "effects.blur", name: "--hf-color-grading-blur", min: 0, max: 1 },
597
+ { path: "effects.bloom", name: "--hf-color-grading-bloom", min: 0, max: 3 },
598
+ { path: "effects.kuwahara", name: "--hf-color-grading-kuwahara", min: 0, max: 1 },
599
+ { path: "effects.pixelate", name: "--hf-color-grading-pixelate", min: 0, max: 1 },
600
+ { path: "effects.ascii", name: "--hf-color-grading-ascii", min: 0, max: 1 },
601
+ { path: "effects.dither", name: "--hf-color-grading-dither", min: 0, max: 1 },
602
+ ];
603
+ function effectLimit(key) {
604
+ return EFFECT_LIMIT_OVERRIDES[key] ?? UNIT_LIMIT;
605
+ }
606
+ const PALETTE_EFFECT_KEYS = new Set([
607
+ "ascii",
608
+ "dither",
609
+ "monoScreen",
610
+ "engraving",
611
+ "crosshatch",
612
+ ]);
613
+ const MULTIPASS_EFFECT_KEYS = new Set(["blur", "bloom", "kuwahara"]);
614
+ /** Agent-readable view of the canonical grading and effect contracts. */
615
+ export function getHfColorGradingCapabilities() {
616
+ return {
617
+ version: 1,
618
+ targetTags: ["img", "video"],
619
+ colorSpace: HF_COLOR_GRADING_COLOR_SPACE,
620
+ intensity: { identity: 1, min: 0, max: 1 },
621
+ palette: { minColors: 2, maxColors: 6, colorFormat: "#rrggbb" },
622
+ lut: {
623
+ format: "3d-cube",
624
+ maxCubeSize: DEFAULT_MAX_CUBE_LUT_SIZE,
625
+ intensity: { ...UNIT_LIMIT },
626
+ },
627
+ presets: [...HF_COLOR_GRADING_GRADE_PRESETS, ...HF_COLOR_GRADING_EFFECT_PRESETS].map((preset) => ({
628
+ id: preset.id,
629
+ label: preset.label,
630
+ intensity: preset.intensity,
631
+ })),
632
+ adjustments: HF_COLOR_GRADING_ADJUST_KEYS.map((key) => ({
633
+ key,
634
+ identity: ADJUST_ZERO[key],
635
+ ...ADJUST_LIMITS[key],
636
+ })),
637
+ effectFamilies: EFFECT_FAMILIES,
638
+ finishing: HF_COLOR_GRADING_DETAIL_KEYS.map((key) => ({
639
+ key,
640
+ identity: DETAIL_DEFAULTS[key],
641
+ ...DETAIL_LIMITS[key],
642
+ })),
643
+ effects: HF_COLOR_GRADING_ACTIVE_EFFECT_KEYS.map((key) => {
644
+ const apply = HF_COLOR_GRADING_EFFECT_APPLY_DEFAULTS[key];
645
+ return {
646
+ key,
647
+ ...EFFECT_METADATA[key],
648
+ apply: { ...apply },
649
+ supportsPalette: PALETTE_EFFECT_KEYS.has(key),
650
+ renderLane: MULTIPASS_EFFECT_KEYS.has(key) ? "multipass" : "single-pass",
651
+ controls: HF_COLOR_GRADING_EFFECT_KEYS.filter((control) => Object.hasOwn(apply, control)).map((control) => ({
652
+ key: control,
653
+ identity: EFFECT_DEFAULTS[control],
654
+ recommended: apply[control] ?? EFFECT_DEFAULTS[control],
655
+ ...effectLimit(control),
656
+ })),
657
+ };
658
+ }),
659
+ palettes: HF_COLOR_GRADING_PALETTES.map(({ id, label, group, colors }) => ({
660
+ id,
661
+ label,
662
+ group,
663
+ colors,
664
+ })),
665
+ animatable: HF_COLOR_GRADING_ANIMATABLE_PROPERTIES,
666
+ };
667
+ }
668
+ const EFFECT_PALETTE_COLOR = /^#[0-9a-f]{6}$/i;
669
+ function normalizePalette(value) {
670
+ if (!Array.isArray(value) || value.length < 2 || value.length > 6)
671
+ return null;
672
+ if (!value.every((color) => typeof color === "string" && EFFECT_PALETTE_COLOR.test(color))) {
673
+ return null;
674
+ }
675
+ return value.map((color) => color.toLowerCase());
676
+ }
224
677
  function isRecord(value) {
225
678
  return typeof value === "object" && value !== null && !Array.isArray(value);
226
679
  }
@@ -278,7 +731,7 @@ function readColorGradingObject(raw) {
278
731
  return null;
279
732
  }
280
733
  }
281
- return { preset: trimmed, intensity: 1 };
734
+ return { preset: trimmed };
282
735
  }
283
736
  return isRecord(raw) ? raw : null;
284
737
  }
@@ -326,8 +779,8 @@ export function normalizeHfColorGrading(raw) {
326
779
  const presetId = normalizePresetId(grading.preset);
327
780
  const preset = getHfColorGradingPreset(presetId);
328
781
  const presetAdjust = preset?.adjust ?? ADJUST_ZERO;
329
- const presetDetails = preset?.details ?? DETAIL_ZERO;
330
- const presetEffects = preset?.effects ?? EFFECT_ZERO;
782
+ const presetDetails = preset?.details ?? DETAIL_DEFAULTS;
783
+ const presetEffects = preset?.effects ?? EFFECT_DEFAULTS;
331
784
  const rawAdjust = isRecord(grading.adjust) ? grading.adjust : {};
332
785
  const rawDetails = isRecord(grading.details) ? grading.details : {};
333
786
  const rawEffects = isRecord(grading.effects) ? grading.effects : {};
@@ -338,18 +791,19 @@ export function normalizeHfColorGrading(raw) {
338
791
  const details = HF_COLOR_GRADING_DETAIL_KEYS.reduce((result, key) => {
339
792
  result[key] = readLimitedValue(rawDetails[key] ?? presetDetails[key], DETAIL_LIMITS[key]);
340
793
  return result;
341
- }, { ...DETAIL_ZERO });
794
+ }, { ...DETAIL_DEFAULTS });
342
795
  const effects = HF_COLOR_GRADING_EFFECT_KEYS.reduce((result, key) => {
343
- result[key] = readLimitedValue(rawEffects[key] ?? presetEffects[key], EFFECT_LIMITS[key]);
796
+ result[key] = readLimitedValue(rawEffects[key] ?? presetEffects[key], EFFECT_LIMIT_OVERRIDES[key] ?? UNIT_LIMIT);
344
797
  return result;
345
- }, { ...EFFECT_ZERO });
798
+ }, { ...EFFECT_DEFAULTS });
346
799
  return {
347
800
  enabled: true,
348
801
  preset: presetId,
349
- intensity: clampUnit(grading.intensity, 1),
802
+ intensity: clampUnit(grading.intensity, preset?.intensity ?? 1),
350
803
  adjust,
351
804
  details,
352
805
  effects,
806
+ palette: normalizePalette(grading.palette),
353
807
  lut: normalizeLut(grading.lut),
354
808
  colorSpace: typeof grading.colorSpace === "string" && grading.colorSpace.trim()
355
809
  ? grading.colorSpace.trim()
@@ -363,19 +817,21 @@ export function serializeHfColorGrading(grading) {
363
817
  const normalized = normalizeHfColorGrading(grading);
364
818
  if (!normalized)
365
819
  return "";
366
- const { enabled: _enabled, ...serializable } = normalized;
367
- return JSON.stringify(serializable);
820
+ const { enabled: _enabled, palette, ...serializable } = normalized;
821
+ return JSON.stringify(palette ? { ...serializable, palette } : serializable);
368
822
  }
369
823
  export function isHfColorGradingActive(grading) {
370
824
  if (!grading?.enabled)
371
825
  return false;
826
+ const hasIndependentTreatment = Math.abs(grading.details.vignette) > 0.0001 ||
827
+ Math.abs(grading.details.grain) > 0.0001 ||
828
+ HF_COLOR_GRADING_ACTIVE_EFFECT_KEYS.some((key) => Math.abs(grading.effects[key]) > 0.0001);
829
+ if (hasIndependentTreatment)
830
+ return true;
372
831
  if (grading.intensity === 0)
373
832
  return false;
374
833
  if (grading.lut && grading.lut.intensity !== 0)
375
834
  return true;
376
- return (HF_COLOR_GRADING_ADJUST_KEYS.some((key) => Math.abs(grading.adjust[key]) > 0.0001) ||
377
- Math.abs(grading.details.vignette) > 0.0001 ||
378
- Math.abs(grading.details.grain) > 0.0001 ||
379
- HF_COLOR_GRADING_EFFECT_KEYS.some((key) => Math.abs(grading.effects[key]) > 0.0001));
835
+ return HF_COLOR_GRADING_ADJUST_KEYS.some((key) => Math.abs(grading.adjust[key]) > 0.0001);
380
836
  }
381
837
  //# sourceMappingURL=colorGrading.js.map