Files
Ewald a4154ccf05 Fix P0 bugs, remove Append V2/Lens Artifacts, and fix crop reload reset
- Fix batch bypass tuple handling, memory budget device-awareness, grain
  RNG device, roto payload limits, preview temp-file cleanup, and route
  double-registration.
- Add native VIDEO output (fps/audio) to effect nodes that previously
  only returned IMAGE/MASK when fed a VIDEO input.
- Split the oversized src/preview/ops/geometry.ts and add a starting
  golden-fixture test harness (spherize, transform) comparing backend
  and frontend math.
- Remove ImageOpsAppendV2 (Autogrow) and ImageOpsLensArtifacts; inline
  Append's helper back into its single caller.
- Collapse the Color Correct curve widget behind a toggle so the node
  no longer grows too tall.
- Fix Crop/generic-node video preview playback running at the RAF rate
  instead of the source video's real fps.
- Fix Crop losing its saved output size after a workflow reload:
  syncCropWidgets recomputed height from width and a preset aspect
  ratio on every resync (including onConfigure on load), discarding
  the restored value. It now only refreshes aspect-ratio bookkeeping
  on a resync.
2026-09-30 12:49:42 +02:00

270 lines
8.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { getPreviewConfig } from "../config.js";
import { isNode as isPreviewNode } from "../nodes/preview.js";
import { isStressed } from "./fps-monitor.js";
function createInitialState(node) {
return {
hooked: false,
_ownerNode: node,
_ownerNodeId: node.id,
_abortController: null,
previewRoot: null,
previewMetaRow: null,
previewControls: null,
canvas: null,
info: null,
progressWrap: null,
progressBar: null,
mediaWrap: null,
mediaVideo: null,
mediaImage: null,
interactionUntil: 0,
rafId: null,
debounceTimer: null,
lastKey: null,
lastRenderTick: null,
renderInFlight: false,
queuedRenderTick: null,
isPreview: isPreviewNode(node),
nativeAnimated: false,
nativeDirty: false,
previewZoom: 1,
previewPanX: 0,
previewPanY: 0,
previewPanDrag: null,
previewNavigationHooked: false,
previewLastSource: null,
previewSourceWidth: 0,
previewSourceHeight: 0,
previewFrameIndex: null,
previewFrameCount: 1,
previewCompareMode: "live",
previewWipeFraction: 0.5,
previewCompareHooked: false,
previewWipeDrag: null,
cropAspectRatio: null,
cropGeometry: null,
cropDrag: null,
cropResetButton: null,
cropInteractiveHooked: false,
rampGeometry: null,
rampDrag: null,
rampInteractiveHooked: false,
drawAspectRatio: null,
drawGeometry: null,
drawHover: null,
drawSizeHintUntil: 0,
drawStroke: null,
drawCanvas: null,
drawBaseCanvas: null,
drawUndoStack: [],
drawOverlayKey: null,
drawBrushButton: null,
drawEraserButton: null,
drawClearButton: null,
drawColorInput: null,
drawEdgeSelect: null,
drawSoftnessInput: null,
drawSoftnessLabel: null,
drawOpacityInput: null,
drawOpacityLabel: null,
drawSizeInput: null,
drawSizeLabel: null,
drawWidthInput: null,
drawHeightInput: null,
drawLinkButton: null,
drawBgColorInput: null,
drawOverlayFormatSelect: null,
drawPressureSizeInput: null,
drawPressureOpacityInput: null,
drawTiltSizeInput: null,
drawInteractiveHooked: false,
colorWheelCanvas: null,
colorHueLabel: null,
colorSatLabel: null,
colorSwatch: null,
colorResetButton: null,
colorTemperatureInput: null,
colorTemperatureLabel: null,
colorTintInput: null,
colorTintLabel: null,
colorHueInput: null,
colorHueValueLabel: null,
colorContrastInput: null,
colorContrastLabel: null,
colorSaturationInput: null,
colorSaturationValueLabel: null,
colorVibranceInput: null,
colorVibranceLabel: null,
colorGammaInput: null,
colorGammaLabel: null,
colorShadowWheelCanvas: null,
colorShadowLabel: null,
colorMidtoneWheelCanvas: null,
colorMidtoneLabel: null,
colorHighlightWheelCanvas: null,
colorHighlightLabel: null,
colorBrightnessInput: null,
colorBrightnessLabel: null,
colorZoneTabsRow: null,
colorZoneTabGlobal: null,
colorZoneTabShadows: null,
colorZoneTabMidtones: null,
colorZoneTabHighlights: null,
colorActiveZone: "global",
colorInteractiveHooked: false,
colorCurveToggleAdded: false,
compLayers: [],
compOutputWidth: 1,
compOutputHeight: 1,
compSelectedSlot: null,
compDrag: null,
compAddButton: null,
compRemoveButton: null,
compResetButton: null,
compResizeButton: null,
compCornerPinButton: null,
compAspectRatioSelect: null,
compModeSelect: null,
compOpacityInput: null,
compOpacityLabel: null,
compLayerLabel: null,
compEditMode: "resize",
compInteractiveHooked: false,
cornerPinGeometry: null,
cornerPinDrag: null,
cornerPinInteractiveHooked: false,
rotoGeometry: null,
rotoHooked: false,
rotoUi: null,
padOutGeometry: null,
padOutDrag: null,
padOutInteractiveHooked: false,
padOutSourceWidth: 0,
padOutSourceHeight: 0,
padOutSourceCanvas: null,
padOutBackendSourceW: 0,
padOutBackendSourceH: 0,
padOutBackendPadL: 0,
padOutBackendPadT: 0,
frameSelectorControls: null,
frameSelectorLabel: null,
frameSelectorTrimStart: null,
frameSelectorTrimEnd: null,
frameSelectorHoldFrame: null,
frameSelectorRuler: null,
frameSelectorSliderBox: null,
frameSelectorFill: null,
frameSelectorStartHandle: null,
frameSelectorEndHandle: null,
frameSelectorPlayhead: null,
frameSelectorHoldToggle: null,
frameSelectorRepeatToggle: null,
frameSelectorRepeatModeSelect: null,
frameSelectorRepeatCountInput: null,
frameSelectorSourceCount: 0,
frameSelectorHooked: false,
keyerControls: null,
keyerModeButtons: [],
keyerInvertButton: null,
keyerInvertMaskButton: null,
keyerBypassButton: null,
keyerPickButton: null,
keyerColorInput: null,
keyerToleranceInput: null,
keyerSoftnessInput: null,
keyerGainInput: null,
keyerBlurInput: null,
keyerPicking: false,
keyerHooked: false
};
}
function ensureState(node) {
const existing = node.__imageops_state;
if (!existing) {
node.__imageops_state = createInitialState(node);
} else if (existing._ownerNode && existing._ownerNode !== node) {
node.__imageops_state = createInitialState(node);
} else {
existing._ownerNode = node;
existing._ownerNodeId = node.id;
}
return node.__imageops_state;
}
function setInfo(st, text) {
if (!st.info) return;
const width = Math.max(0, Math.round(Number(st.previewSourceWidth) || 0));
const height = Math.max(0, Math.round(Number(st.previewSourceHeight) || 0));
const hasSize = /\b\d+\s*x\s*\d+\b/i.test(text) || /\b\d+\s*×\s*\d+\b/i.test(text);
st.info.textContent = !hasSize && width > 0 && height > 0 ? `${text} (${width}x${height})` : text;
}
function markPreviewInteraction(node, holdMs = 350) {
const st = ensureState(node);
st.interactionUntil = Math.max(st.interactionUntil ?? 0, performance.now() + holdMs);
}
function getRenderCanvasSize(st) {
const cfg = getPreviewConfig();
const now = performance.now();
const stressed = isStressed();
if ((st.interactionUntil ?? 0) > now) {
return stressed ? Math.max(1, Math.round(cfg.interactionCanvasSize * 0.75)) : cfg.interactionCanvasSize;
}
if (st.rafId != null) {
return stressed ? Math.max(1, Math.round(cfg.playbackCanvasSize * 0.75)) : cfg.playbackCanvasSize;
}
return stressed ? Math.max(1, Math.round(cfg.canvasSize * 0.85)) : cfg.canvasSize;
}
function hashText(value) {
let hash = 2166136261;
for (let index = 0; index < value.length; index++) {
hash ^= value.charCodeAt(index);
hash = Math.imul(hash, 16777619);
}
return (hash >>> 0).toString(16);
}
function serializePreviewValue(value) {
if (value == null) return "null";
if (typeof value === "string") {
return value.length > 120 ? `str:${value.length}:${hashText(value)}` : `str:${value}`;
}
if (typeof value === "number" || typeof value === "boolean") return String(value);
if (Array.isArray(value)) {
return `arr:${value.length}:${value.map((entry) => serializePreviewValue(entry)).join(",")}`;
}
try {
const json = JSON.stringify(value);
if (!json) return String(value);
return json.length > 120 ? `json:${json.length}:${hashText(json)}` : `json:${json}`;
} catch {
return String(value);
}
}
function buildPreviewRenderKey(node, tick, st, renderCanvasSize) {
const parts = [
String(node.id),
String(node.comfyClass ?? ""),
`tick:${tick}`,
`canvas:${renderCanvasSize}`,
`dirty:${st.nativeDirty ? 1 : 0}`,
`anim:${st.nativeAnimated ? 1 : 0}`,
`imageIndex:${typeof node.imageIndex === "number" ? node.imageIndex : -1}`,
`imgs:${Array.isArray(node.imgs) ? node.imgs.length : 0}`
];
for (const widget of node.widgets ?? []) {
parts.push(`${widget?.name ?? "widget"}=${serializePreviewValue(widget?.value)}`);
}
for (let index = 0; index < (node.inputs?.length ?? 0); index++) {
const input = node.inputs?.[index];
parts.push(`in${index}:${input?.name ?? ""}:${input?.link ?? "null"}`);
}
return parts.join("|");
}
export {
buildPreviewRenderKey,
ensureState,
getRenderCanvasSize,
hashText,
markPreviewInteraction,
serializePreviewValue,
setInfo
};