Add frontend preset sync for LCSToneAdjust sliders

Selecting a preset now updates slider values in real-time via a
frontend JS extension, and users can tweak values after selecting.
Sliders are always visible (removed optional=True). Preset values
live only in JS (single source of truth); Python keeps just the
name list for the combo widget. Sync only fires on explicit user
dropdown interaction, preserving saved slider tweaks on workflow load.
This commit is contained in:
facok
2026-03-17 04:47:57 +08:00
parent 51becf21e4
commit 78be03db46
3 changed files with 73 additions and 36 deletions
+3
View File
@@ -34,9 +34,12 @@ async def comfy_entrypoint() -> LCSExtension:
# V2 backward compatibility
from .nodes import NODE_CLASS_MAPPINGS, NODE_DISPLAY_NAME_MAPPINGS
WEB_DIRECTORY = "./web"
__all__ = [
"NODE_CLASS_MAPPINGS",
"NODE_DISPLAY_NAME_MAPPINGS",
"WEB_DIRECTORY",
"LCSExtension",
"comfy_entrypoint",
]
+21 -36
View File
@@ -440,18 +440,12 @@ def _build_tone_fn(lcs_data, contrast, brightness, saturation, color_temperature
return post_cfg_fn
TONE_PRESETS = {
"Cinematic": {"contrast": 1.20, "brightness": -0.05, "saturation": 0.90, "color_temperature": 0.15},
"HDR": {"contrast": 1.40, "brightness": 0.0, "saturation": 1.20, "color_temperature": 0.0},
"Vivid": {"contrast": 1.10, "brightness": 0.0, "saturation": 1.50, "color_temperature": 0.0},
"Dramatic": {"contrast": 1.50, "brightness": -0.10, "saturation": 0.85, "color_temperature": 0.0},
"Low Key": {"contrast": 1.30, "brightness": -0.20, "saturation": 0.80, "color_temperature": 0.0},
"High Key": {"contrast": 0.80, "brightness": 0.20, "saturation": 0.90, "color_temperature": 0.0},
"Warm": {"contrast": 1.0, "brightness": 0.0, "saturation": 1.0, "color_temperature": 0.50},
"Cool": {"contrast": 1.0, "brightness": 0.0, "saturation": 1.0, "color_temperature": -0.50},
"Desaturated": {"contrast": 1.0, "brightness": 0.0, "saturation": 0.40, "color_temperature": 0.0},
"Custom": None,
}
# Preset names for the combo widget. Actual values live in web/js/tone_preset.js
# which syncs them into the slider widgets on the frontend.
TONE_PRESET_NAMES = [
"Cinematic", "HDR", "Vivid", "Dramatic", "Low Key",
"High Key", "Warm", "Cool", "Desaturated", "Custom",
]
class LCSToneAdjust(io.ComfyNode):
@@ -476,16 +470,16 @@ class LCSToneAdjust(io.ComfyNode):
inputs=[
io.Model.Input("model"),
LCS_DATA.Input("lcs_data", tooltip="Calibration data from LCSCalibrate or LCSLoadData"),
io.Combo.Input("preset", options=list(TONE_PRESETS.keys()), default="Custom",
io.Combo.Input("preset", options=TONE_PRESET_NAMES, default="Custom",
tooltip="Select a tonal preset or Custom to use the sliders below"),
io.Float.Input("contrast", default=1.0, min=0.0, max=3.0, step=0.05, optional=True,
tooltip="[Custom only] Lightness contrast multiplier (>1 = more contrast, <1 = less, 1 = no change)"),
io.Float.Input("brightness", default=0.0, min=-1.0, max=1.0, step=0.05, optional=True,
tooltip="[Custom only] Lightness shift (>0 = brighter, <0 = darker)"),
io.Float.Input("saturation", default=1.0, min=0.0, max=3.0, step=0.05, optional=True,
tooltip="[Custom only] Saturation multiplier (>1 = more vivid, <1 = more muted, 0 = grayscale)"),
io.Float.Input("color_temperature", default=0.0, min=-2.0, max=2.0, step=0.05, optional=True,
tooltip="[Custom only] Color temperature shift (>0 = warmer/amber, <0 = cooler/blue)"),
io.Float.Input("contrast", default=1.0, min=0.0, max=3.0, step=0.05,
tooltip="Lightness contrast multiplier (>1 = more contrast, <1 = less, 1 = no change)"),
io.Float.Input("brightness", default=0.0, min=-1.0, max=1.0, step=0.05,
tooltip="Lightness shift (>0 = brighter, <0 = darker)"),
io.Float.Input("saturation", default=1.0, min=0.0, max=3.0, step=0.05,
tooltip="Saturation multiplier (>1 = more vivid, <1 = more muted, 0 = grayscale)"),
io.Float.Input("color_temperature", default=0.0, min=-2.0, max=2.0, step=0.05,
tooltip="Color temperature shift (>0 = warmer/amber, <0 = cooler/blue)"),
io.Int.Input("start_step", default=5, min=0, max=50,
tooltip="First step to apply adjustment"),
io.Int.Input("end_step", default=15, min=0, max=50,
@@ -499,22 +493,13 @@ class LCSToneAdjust(io.ComfyNode):
)
@classmethod
def execute(cls, model, lcs_data, preset, start_step, end_step,
contrast=None, brightness=None, saturation=None, color_temperature=None,
mask=None) -> io.NodeOutput:
"""Clone model, attach LCS tone adjustment hook. Returns patched MODEL."""
if preset != "Custom":
p = TONE_PRESETS[preset]
contrast = p["contrast"]
brightness = p["brightness"]
saturation = p["saturation"]
color_temperature = p["color_temperature"]
else:
contrast = contrast if contrast is not None else 1.0
brightness = brightness if brightness is not None else 0.0
saturation = saturation if saturation is not None else 1.0
color_temperature = color_temperature if color_temperature is not None else 0.0
def execute(cls, model, lcs_data, preset, contrast, brightness, saturation,
color_temperature, start_step, end_step, mask=None) -> io.NodeOutput:
"""Clone model, attach LCS tone adjustment hook. Returns patched MODEL.
The preset combo sets slider values via frontend JS; execute() always
uses the actual slider values so the user can tweak after selecting a preset.
"""
m = model.clone()
# Skip hook entirely when all parameters are at default (true no-op)
if not _is_default_tone(contrast, brightness, saturation, color_temperature):
+49
View File
@@ -0,0 +1,49 @@
import { app } from "../../../scripts/app.js";
// Preset values — the single source of truth for preset parameters.
// TONE_PRESET_NAMES in nodes/intervene.py must list matching keys.
const TONE_PRESETS = {
"Cinematic": { contrast: 1.20, brightness: -0.05, saturation: 0.90, color_temperature: 0.15 },
"HDR": { contrast: 1.40, brightness: 0.0, saturation: 1.20, color_temperature: 0.0 },
"Vivid": { contrast: 1.10, brightness: 0.0, saturation: 1.50, color_temperature: 0.0 },
"Dramatic": { contrast: 1.50, brightness: -0.10, saturation: 0.85, color_temperature: 0.0 },
"Low Key": { contrast: 1.30, brightness: -0.20, saturation: 0.80, color_temperature: 0.0 },
"High Key": { contrast: 0.80, brightness: 0.20, saturation: 0.90, color_temperature: 0.0 },
"Warm": { contrast: 1.0, brightness: 0.0, saturation: 1.0, color_temperature: 0.50 },
"Cool": { contrast: 1.0, brightness: 0.0, saturation: 1.0, color_temperature: -0.50 },
"Desaturated": { contrast: 1.0, brightness: 0.0, saturation: 0.40, color_temperature: 0.0 },
};
const SLIDER_NAMES = ["contrast", "brightness", "saturation", "color_temperature"];
function syncPreset(node, presetName) {
const preset = TONE_PRESETS[presetName];
if (!preset) return; // "Custom" — leave sliders as-is
for (const name of SLIDER_NAMES) {
const widget = node.widgets?.find(w => w.name === name);
if (widget && name in preset) {
widget.value = preset[name];
}
}
node.graph?.setDirtyCanvas(true, true);
}
app.registerExtension({
name: "ComfyUI-LCS.TonePresetSync",
nodeCreated(node) {
if (node.comfyClass !== "LCSToneAdjust") return;
const presetWidget = node.widgets?.find(w => w.name === "preset");
if (!presetWidget) return;
// Only sync on explicit user interaction (dropdown change), not on
// workflow load or paste — those restore saved slider values directly.
const origCallback = presetWidget.callback;
presetWidget.callback = function (value, canvas, nodeRef, pos, event) {
if (origCallback) origCallback.call(this, value, canvas, nodeRef, pos, event);
syncPreset(node, value);
};
},
});