Files
MajoorWaldi-ComfyUI-Majoor-…/js/preview/shared/state.js
T
Ewald 23dcd1c2d9 Add lens/compositing effect nodes and depth-based Defocus, A/B compare on Preview
- Add ImageOpsVignette, ImageOpsChromaticAberration, ImageOpsBloom, and
  ImageOpsLensArtifacts for common lens/compositing effects.
- Add ImageOpsDefocus: depth-driven defocus/bokeh with shaped aperture
  kernels (circle/hexagon/octagon/custom) and highlight bloom.
- Add input_space (srgb/linear) to ImageOpsColorAjust so scene-linear/HDR
  sources aren't gamma-encoded twice.
- Add image_b + compare_mode/wipe_position to ImageOpsPreview for a real
  A/B comparison, plus a separate live on-demand A/B toggle while editing.
- Redesign the live-preview HUD: unified pill-style badges, frame counter
  now shows the real total and hides for single-frame sources, and the
  Output overlay only appears for Pad Out with the real pixel size.
- Align README with the current node set and fix stale blend-mode docs.
2026-09-28 00:19:49 +02:00

266 lines
8.0 KiB
JavaScript
Raw 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,
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,
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
};