mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 12:54:29 +00:00
306 lines
8.7 KiB
TypeScript
306 lines
8.7 KiB
TypeScript
import {
|
|
getHfColorGradingCapabilities,
|
|
normalizeHfColorGrading,
|
|
type HfColorGradingActiveEffectKey,
|
|
type HfColorGradingEffectKey,
|
|
type HfColorGradingPresetId,
|
|
} from "@hyperframes/core/color-grading";
|
|
|
|
type SliderControl = {
|
|
kind: "slider";
|
|
key: HfColorGradingEffectKey;
|
|
label: string;
|
|
min?: number;
|
|
max?: number;
|
|
step?: number;
|
|
scale?: number;
|
|
unit?: string;
|
|
format?: (value: number) => string;
|
|
};
|
|
|
|
export type EffectControl =
|
|
| SliderControl
|
|
| { kind: "toggle"; key: HfColorGradingEffectKey; label: string }
|
|
| {
|
|
kind: "select";
|
|
key: HfColorGradingEffectKey;
|
|
label: string;
|
|
options: Array<{ value: string; label: string }>;
|
|
};
|
|
|
|
export type EffectSpec = {
|
|
key: HfColorGradingActiveEffectKey;
|
|
label: string;
|
|
showMaster?: false;
|
|
masterLabel?: string;
|
|
masterFormat?: (value: number) => string;
|
|
max?: number;
|
|
settings?: readonly EffectControl[];
|
|
palette?: "mono" | "art";
|
|
};
|
|
|
|
type EffectGroup = {
|
|
label: string;
|
|
effects: readonly EffectSpec[];
|
|
presets?: readonly HfColorGradingPresetId[];
|
|
};
|
|
|
|
const EFFECT_CONTROL_LIMITS = new Map(
|
|
getHfColorGradingCapabilities().effects.flatMap((effect) =>
|
|
effect.controls.map((control) => [control.key, control] as const),
|
|
),
|
|
);
|
|
|
|
function controlRange(key: HfColorGradingEffectKey, scale: number) {
|
|
const control = EFFECT_CONTROL_LIMITS.get(key);
|
|
return control ? { min: control.min * scale, max: control.max * scale, scale } : { scale };
|
|
}
|
|
|
|
const percent = (key: HfColorGradingEffectKey, label: string): SliderControl => ({
|
|
kind: "slider",
|
|
key,
|
|
label,
|
|
...controlRange(key, 100),
|
|
});
|
|
|
|
const degrees = (key: HfColorGradingEffectKey, label: string, max: number): SliderControl => ({
|
|
kind: "slider",
|
|
key,
|
|
label,
|
|
...controlRange(key, max),
|
|
unit: "deg",
|
|
});
|
|
|
|
function enumOptions(key: HfColorGradingEffectKey, labels: readonly string[]) {
|
|
const control = EFFECT_CONTROL_LIMITS.get(key);
|
|
const first = control?.min ?? 0;
|
|
const count = (control?.max ?? labels.length - 1) - first + 1;
|
|
if (labels.length !== count) throw new Error(`${key} labels do not match Core capabilities`);
|
|
return labels.map((label, index) => ({ value: String(first + index), label }));
|
|
}
|
|
|
|
const ASCII_STYLES = enumOptions("asciiStyle", [
|
|
"Standard",
|
|
"Dense",
|
|
"Minimal",
|
|
"Blocks",
|
|
"Braille",
|
|
"Technical",
|
|
"Matrix",
|
|
"Hatching",
|
|
]);
|
|
|
|
const SCREEN_SHAPES = enumOptions("monoScreenShape", [
|
|
"Circle",
|
|
"Square",
|
|
"Diamond",
|
|
"Triangle",
|
|
"Line",
|
|
]);
|
|
|
|
export const EFFECT_GROUPS: readonly EffectGroup[] = [
|
|
{
|
|
label: "Essentials",
|
|
effects: [
|
|
{
|
|
key: "blur",
|
|
label: "Blur",
|
|
masterLabel: "Radius",
|
|
masterFormat: (value) => `${(0.75 + Math.pow(value, 1.35) * 32).toFixed(1)}px`,
|
|
},
|
|
{
|
|
key: "pixelate",
|
|
label: "Pixelate",
|
|
masterLabel: "Cell Size",
|
|
masterFormat: (value) => `${Math.round(1 + value * 47)}px`,
|
|
},
|
|
{
|
|
key: "bloom",
|
|
label: "Bloom",
|
|
masterLabel: "Intensity",
|
|
max: controlRange("bloom", 100).max,
|
|
settings: [
|
|
{
|
|
kind: "slider",
|
|
key: "bloomRadius",
|
|
label: "Radius",
|
|
...controlRange("bloomRadius", 1),
|
|
unit: "px",
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
label: "Retro & Glitch",
|
|
presets: ["creator-camcorder", "vhs-playback", "home-movie-8mm"],
|
|
effects: [
|
|
{ key: "chromaBleed", label: "Chroma Softening", masterLabel: "Smear" },
|
|
{
|
|
key: "tapeDamage",
|
|
label: "Tape Damage",
|
|
showMaster: false,
|
|
settings: [
|
|
percent("tapeTracking", "Tracking"),
|
|
percent("tapeNoise", "Noise"),
|
|
percent("tapeSpeed", "Speed"),
|
|
],
|
|
},
|
|
{ key: "filmArtifacts", label: "Film Artifacts", masterLabel: "Density" },
|
|
{
|
|
key: "scanlines",
|
|
label: "Scanlines",
|
|
masterLabel: "Opacity",
|
|
settings: [
|
|
{
|
|
...percent("scanlineCount", "Line Count"),
|
|
format: (value) => `${Math.round(50 + value * 450)}`,
|
|
},
|
|
percent("scanlineSoftness", "Softness"),
|
|
],
|
|
},
|
|
{ key: "crtCurvature", label: "CRT Curvature", masterLabel: "Curvature" },
|
|
{
|
|
key: "chromaticAberration",
|
|
label: "Channel Separation",
|
|
masterLabel: "Separation",
|
|
settings: [degrees("chromaticAngle", "Angle", 360)],
|
|
},
|
|
{
|
|
key: "digitalGlitch",
|
|
label: "Digital Glitch",
|
|
showMaster: false,
|
|
settings: [
|
|
percent("digitalGlitchColorSplit", "Color Split"),
|
|
percent("digitalGlitchLineTear", "Line Tear"),
|
|
percent("digitalGlitchPixelate", "Pixelation"),
|
|
percent("digitalGlitchBlockAmount", "Block Amount"),
|
|
percent("digitalGlitchBlockDisplacement", "Displacement"),
|
|
percent("digitalGlitchBlockOpacity", "Block Opacity"),
|
|
percent("digitalGlitchSpeed", "Speed"),
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
label: "Print",
|
|
presets: ["editorial-halftone", "two-ink-print"],
|
|
effects: [
|
|
{
|
|
key: "halftone",
|
|
label: "Halftone",
|
|
showMaster: false,
|
|
settings: [percent("halftoneSize", "Dot Size")],
|
|
},
|
|
{
|
|
key: "twoInkPrint",
|
|
label: "Two-Ink Print",
|
|
showMaster: false,
|
|
settings: [percent("twoInkPrintSize", "Dot Size")],
|
|
},
|
|
{
|
|
key: "dither",
|
|
label: "Ordered Dither",
|
|
showMaster: false,
|
|
palette: "mono",
|
|
settings: [
|
|
{
|
|
...percent("ditherSize", "Point Size"),
|
|
format: (value) => `${(1 + value * 4).toFixed(1)}px`,
|
|
},
|
|
],
|
|
},
|
|
{
|
|
key: "monoScreen",
|
|
label: "Mono Screen",
|
|
showMaster: false,
|
|
palette: "mono",
|
|
settings: [
|
|
{
|
|
...percent("monoScreenSize", "Cell Size"),
|
|
format: (value) => `${Math.round(4 + value * 14)}px`,
|
|
},
|
|
degrees("monoScreenAngle", "Angle", 90),
|
|
percent("monoScreenSpread", "Spread"),
|
|
{ kind: "select", key: "monoScreenShape", label: "Shape", options: SCREEN_SHAPES },
|
|
{ kind: "toggle", key: "monoScreenInvert", label: "Invert" },
|
|
],
|
|
},
|
|
],
|
|
},
|
|
{
|
|
label: "Art",
|
|
effects: [
|
|
{
|
|
key: "ascii",
|
|
label: "ASCII",
|
|
showMaster: false,
|
|
palette: "mono",
|
|
settings: [
|
|
{
|
|
...percent("asciiSize", "Character Size"),
|
|
format: (value) => `${Math.round(4 + value * 76)}px`,
|
|
},
|
|
{ kind: "select", key: "asciiStyle", label: "Style", options: ASCII_STYLES },
|
|
{ kind: "toggle", key: "asciiInvert", label: "Invert" },
|
|
{ kind: "toggle", key: "asciiColor", label: "Use Source Color" },
|
|
percent("asciiRotation", "Edge Rotation"),
|
|
],
|
|
},
|
|
{
|
|
key: "engraving",
|
|
label: "Engraving",
|
|
showMaster: false,
|
|
palette: "art",
|
|
settings: [
|
|
percent("engravingSpacing", "Spacing"),
|
|
percent("engravingMinThickness", "Min Thickness"),
|
|
percent("engravingMaxThickness", "Max Thickness"),
|
|
degrees("engravingAngle", "Angle", 180),
|
|
percent("engravingContrast", "Contrast"),
|
|
percent("engravingSharpness", "Sharpness"),
|
|
percent("engravingWave", "Wave"),
|
|
percent("engravingWaveFrequency", "Wave Frequency"),
|
|
],
|
|
},
|
|
{
|
|
key: "crosshatch",
|
|
label: "Crosshatch",
|
|
showMaster: false,
|
|
palette: "art",
|
|
settings: [
|
|
percent("crosshatchSpacing", "Spacing"),
|
|
percent("crosshatchThickness", "Thickness"),
|
|
degrees("crosshatchAngle", "Angle", 180),
|
|
percent("crosshatchContrast", "Contrast"),
|
|
percent("crosshatchEdges", "Edge Detail"),
|
|
percent("crosshatchLineWeight", "Line Variation"),
|
|
percent("crosshatchWave", "Wave"),
|
|
percent("crosshatchWaveFrequency", "Wave Frequency"),
|
|
],
|
|
},
|
|
{
|
|
key: "kuwahara",
|
|
label: "Kuwahara Paint",
|
|
showMaster: false,
|
|
settings: [
|
|
{
|
|
...percent("kuwaharaRadius", "Radius"),
|
|
format: (value) => `${Math.round(2 + value * 14)}px`,
|
|
},
|
|
percent("kuwaharaSharpness", "Sharpness"),
|
|
{
|
|
...percent("kuwaharaSaturation", "Saturation"),
|
|
format: (value) => `${Math.round(value * 200)}%`,
|
|
},
|
|
],
|
|
},
|
|
],
|
|
},
|
|
];
|
|
|
|
export const EFFECT_SPECS = EFFECT_GROUPS.flatMap((group) => group.effects);
|
|
const DEFAULT_GRADING = normalizeHfColorGrading("neutral");
|
|
if (!DEFAULT_GRADING) throw new Error("Missing neutral color grading preset");
|
|
export const DEFAULT_EFFECTS = DEFAULT_GRADING.effects;
|