mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 04:38:33 +00:00
fix(core): align secondary mask contracts
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { COLOR_GRADING_ADVANCED_LIMITS } from "@hyperframes/parsers/color-grading-contract";
|
||||
import { unitFloatToByte } from "./colorLuts";
|
||||
import {
|
||||
calculateHfColorGradingSecondaryMask,
|
||||
HF_COLOR_GRADING_COLOR_SPACE,
|
||||
HF_COLOR_GRADING_ACTIVE_EFFECT_KEYS,
|
||||
HF_COLOR_GRADING_EFFECT_APPLY_DEFAULTS,
|
||||
@@ -198,6 +200,37 @@ describe("color grading", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("matches the shader's packed secondary-softness convention", () => {
|
||||
for (const axis of ["saturation", "luma"] as const) {
|
||||
const grading = normalizeHfColorGrading({
|
||||
secondaries: [
|
||||
{
|
||||
key: {
|
||||
hue: { center: 0, range: 180, softness: 0 },
|
||||
saturation: { min: axis === "saturation" ? 0.5 : 0, max: 1, softness: 0.4 },
|
||||
luma: { min: axis === "luma" ? 0.5 : 0, max: 1, softness: 0.4 },
|
||||
},
|
||||
correction: {},
|
||||
},
|
||||
],
|
||||
});
|
||||
const key = grading?.secondaries[0]?.key;
|
||||
if (!key) throw new Error("Expected a normalized secondary key");
|
||||
|
||||
const packedSoftness = unitFloatToByte(key[axis].softness / 0.5);
|
||||
const shaderDecodedSoftness = (packedSoftness / 255) * 0.5;
|
||||
const decodedKey = {
|
||||
...key,
|
||||
[axis]: { ...key[axis], softness: shaderDecodedSoftness },
|
||||
};
|
||||
const productMask = calculateHfColorGradingSecondaryMask(0, 0.2, 0.2, key);
|
||||
const shaderMask = calculateHfColorGradingSecondaryMask(0, 0.2, 0.2, decodedKey);
|
||||
|
||||
expect(productMask, axis).toBeGreaterThan(0);
|
||||
expect(productMask, axis).toBeCloseTo(shaderMask, 2);
|
||||
}
|
||||
});
|
||||
|
||||
it("merges manual adjustments over preset values", () => {
|
||||
const grading = normalizeHfColorGrading({
|
||||
preset: "warm-daylight",
|
||||
|
||||
@@ -675,24 +675,19 @@ const SECONDARY_SOFT_RANGE_DEFAULT: Required<HfColorGradingSoftRange> = {
|
||||
softness: 0.05,
|
||||
};
|
||||
const DETAIL_LIMITS: Record<HfColorGradingDetailKey, { min: number; max: number }> = {
|
||||
vignette: { min: 0, max: 1 },
|
||||
vignetteMidpoint: { min: 0, max: 1 },
|
||||
vignetteRoundness: { min: -1, max: 1 },
|
||||
vignetteFeather: { min: 0, max: 1 },
|
||||
grain: { min: 0, max: 1 },
|
||||
grainSize: { min: 0, max: 1 },
|
||||
grainRoughness: { min: 0, max: 1 },
|
||||
vignette: COLOR_GRADING_ADVANCED_LIMITS.unit,
|
||||
vignetteMidpoint: COLOR_GRADING_ADVANCED_LIMITS.unit,
|
||||
vignetteRoundness: COLOR_GRADING_ADVANCED_LIMITS.signedUnit,
|
||||
vignetteFeather: COLOR_GRADING_ADVANCED_LIMITS.unit,
|
||||
grain: COLOR_GRADING_ADVANCED_LIMITS.unit,
|
||||
grainSize: COLOR_GRADING_ADVANCED_LIMITS.unit,
|
||||
grainRoughness: COLOR_GRADING_ADVANCED_LIMITS.unit,
|
||||
};
|
||||
|
||||
const UNIT_LIMIT = COLOR_GRADING_ADVANCED_LIMITS.unit;
|
||||
const EFFECT_LIMIT_OVERRIDES: Partial<
|
||||
Record<HfColorGradingEffectKey, { min: number; max: number }>
|
||||
> = {
|
||||
asciiStyle: { min: 0, max: 7 },
|
||||
bloom: { min: 0, max: 3 },
|
||||
bloomRadius: { min: 1, max: 100 },
|
||||
monoScreenShape: { min: 0, max: 4 },
|
||||
};
|
||||
> = COLOR_GRADING_ADVANCED_LIMITS.effects;
|
||||
|
||||
export const HF_COLOR_GRADING_ACTIVE_EFFECT_KEYS = [
|
||||
"blur",
|
||||
@@ -1115,6 +1110,47 @@ function wrapDegrees(value: unknown, fallback = 0): number {
|
||||
return ((parsed % 360) + 360) % 360;
|
||||
}
|
||||
|
||||
function smoothstep(min: number, max: number, value: number): number {
|
||||
const normalized = clamp((value - min) / (max - min), 0, 1);
|
||||
return normalized * normalized * (3 - 2 * normalized);
|
||||
}
|
||||
|
||||
function softRangeMask(value: number, min: number, max: number, softness: number): number {
|
||||
if (value < min) {
|
||||
return softness <= 0 ? 0 : smoothstep(min - softness, min, value);
|
||||
}
|
||||
if (value > max) {
|
||||
return softness <= 0 ? 0 : 1 - smoothstep(max, max + softness, value);
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Mirrors the runtime shader's HSL-secondary qualifier for Studio mattes and
|
||||
* other non-WebGL verification surfaces.
|
||||
*/
|
||||
export function calculateHfColorGradingSecondaryMask(
|
||||
hue: number,
|
||||
saturation: number,
|
||||
luma: number,
|
||||
key: NormalizedHfColorGradingSecondary["key"],
|
||||
): number {
|
||||
const hueDistance = Math.abs(((((hue - key.hue.center + 540) % 360) + 360) % 360) - 180);
|
||||
const hueMask =
|
||||
key.hue.range < 179.999 && saturation < 0.001
|
||||
? 0
|
||||
: hueDistance <= key.hue.range
|
||||
? 1
|
||||
: key.hue.softness <= 0
|
||||
? 0
|
||||
: 1 - smoothstep(key.hue.range, key.hue.range + key.hue.softness, hueDistance);
|
||||
return (
|
||||
hueMask *
|
||||
softRangeMask(saturation, key.saturation.min, key.saturation.max, key.saturation.softness) *
|
||||
softRangeMask(luma, key.luma.min, key.luma.max, key.luma.softness)
|
||||
);
|
||||
}
|
||||
|
||||
function normalizeWheels(value: unknown): NormalizedHfColorGradingWheels {
|
||||
const wheels = isRecord(value) ? value : {};
|
||||
return HF_COLOR_GRADING_WHEEL_KEYS.reduce<NormalizedHfColorGradingWheels>(
|
||||
|
||||
@@ -2764,6 +2764,7 @@ const ADVANCED_SIGNATURES = new WeakMap<
|
||||
signature: string;
|
||||
}
|
||||
>();
|
||||
// Identity-keyed: normalized grading objects must be replaced, never mutated.
|
||||
|
||||
function advancedTextureSignature(
|
||||
curves: NormalizedHfColorGradingCurves,
|
||||
|
||||
@@ -26,6 +26,12 @@ describe("color grading contract", () => {
|
||||
secondaryHueRange: { min: 0, max: 180 },
|
||||
secondarySoftRangeSoftness: { min: 0, max: 0.5 },
|
||||
secondaryHueShift: { min: -180, max: 180 },
|
||||
effects: {
|
||||
asciiStyle: { min: 0, max: 7 },
|
||||
bloom: { min: 0, max: 3 },
|
||||
bloomRadius: { min: 1, max: 100 },
|
||||
monoScreenShape: { min: 0, max: 4 },
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -11,6 +11,12 @@ export const COLOR_GRADING_ADVANCED_LIMITS = {
|
||||
secondaryHueCombinedMax: 180,
|
||||
secondarySoftRangeSoftness: { min: 0, max: 0.5 },
|
||||
secondaryHueShift: { min: -180, max: 180 },
|
||||
effects: {
|
||||
asciiStyle: { min: 0, max: 7 },
|
||||
bloom: { min: 0, max: 3 },
|
||||
bloomRadius: { min: 1, max: 100 },
|
||||
monoScreenShape: { min: 0, max: 4 },
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const COLOR_GRADING_TOP_LEVEL_KEYS = [
|
||||
@@ -142,12 +148,8 @@ type NumericLimit = Readonly<{ min: number; max: number }>;
|
||||
|
||||
const UNIT_LIMIT: NumericLimit = COLOR_GRADING_ADVANCED_LIMITS.unit;
|
||||
const SIGNED_UNIT_LIMIT: NumericLimit = COLOR_GRADING_ADVANCED_LIMITS.signedUnit;
|
||||
const EFFECT_LIMIT_OVERRIDES: Readonly<Record<string, NumericLimit>> = {
|
||||
asciiStyle: { min: 0, max: 7 },
|
||||
bloom: { min: 0, max: 3 },
|
||||
bloomRadius: { min: 1, max: 100 },
|
||||
monoScreenShape: { min: 0, max: 4 },
|
||||
};
|
||||
const EFFECT_LIMIT_OVERRIDES: Readonly<Record<string, NumericLimit>> =
|
||||
COLOR_GRADING_ADVANCED_LIMITS.effects;
|
||||
const VARIABLE_REF = /^\$(?:\{[A-Za-z0-9_.:-]+\}|[A-Za-z0-9_.:-]+)$/;
|
||||
const PALETTE_COLOR = /^#[0-9a-f]{6}$/i;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user