384 lines
17 KiB
JavaScript
384 lines
17 KiB
JavaScript
import { app } from "../../scripts/app.js";
|
|
import { api } from "../../scripts/api.js";
|
|
|
|
|
|
function imgToCanvasBase64 (img) {
|
|
const canvas = document.createElement('canvas');
|
|
const ctx = canvas.getContext('2d');
|
|
canvas.width = img.width;
|
|
canvas.height = img.height;
|
|
ctx.drawImage(img, 0, 0);
|
|
const base64 = canvas.toDataURL('image/png');
|
|
return base64;
|
|
}
|
|
|
|
function convertImageToBase64 (img) {
|
|
try {
|
|
const base64 = imgToCanvasBase64(img);
|
|
return base64;
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
}
|
|
|
|
// Helper function to compute SHA-256 hash of a string and return it as hex
|
|
async function computeHash(str) {
|
|
const encoder = new TextEncoder();
|
|
const data = encoder.encode(str);
|
|
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
return hashHex;
|
|
}
|
|
|
|
// Global flag to enable/disable debug logging
|
|
const ENABLE_DEBUG = false;
|
|
|
|
app.registerExtension({
|
|
name: "vrch.ImagePreviewBackgroundNode",
|
|
|
|
async nodeCreated(node) {
|
|
// Only process nodes of type "VrchImagePreviewBackgroundNode"
|
|
if (node.comfyClass === "VrchImagePreviewBackgroundNode") {
|
|
// Get the relevant widgets
|
|
const channelWidget = node.widgets.find(w => w.name === "channel");
|
|
const enableWidget = node.widgets.find(w => w.name === "background_display");
|
|
const intervalWidget = node.widgets.find(w => w.name === "refresh_interval_ms");
|
|
const displayOptionWidget = node.widgets.find(w => w.name === "display_option");
|
|
const batchDisplayWidget = node.widgets.find(w => w.name === "batch_display");
|
|
const batchDisplayIntervalWidget = node.widgets.find(w => w.name === "batch_display_interval_ms");
|
|
const batchImagesSizeWidget = node.widgets.find(w => w.name === "batch_images_size");
|
|
|
|
const widgets = [channelWidget, enableWidget, intervalWidget, displayOptionWidget,
|
|
batchDisplayWidget, batchDisplayIntervalWidget, batchImagesSizeWidget];
|
|
|
|
// Global variable for batch index and timer
|
|
let batchIndex = 0;
|
|
let batchTimer = null;
|
|
|
|
// Function to set up the batch display timer if enabled
|
|
function setupBatchInterval() {
|
|
if (batchTimer) {
|
|
clearInterval(batchTimer);
|
|
batchTimer = null;
|
|
}
|
|
if (batchDisplayWidget && batchDisplayWidget.value) {
|
|
let ms = parseInt(batchDisplayIntervalWidget.value || 1000, 10);
|
|
let imagesSize = parseInt(batchImagesSizeWidget.value || 1, 10);
|
|
// Reset batchIndex to 0 when (re)starting timer
|
|
batchIndex = 0;
|
|
batchTimer = setInterval(() => {
|
|
batchIndex = (batchIndex + 1) % imagesSize;
|
|
reinitBackground();
|
|
updateBackground();
|
|
if (ENABLE_DEBUG) {
|
|
console.log(`Batch display timer updated index to ${batchIndex}`);
|
|
}
|
|
}, ms);
|
|
}
|
|
}
|
|
|
|
// Set callbacks for widget changes to update the background immediately
|
|
widgets.forEach(widget => {
|
|
if (widget) {
|
|
widget.callback = () => {
|
|
if (ENABLE_DEBUG) {
|
|
console.log(`callback:::${widget.name}:::${widget.value}`);
|
|
}
|
|
// For batch related widgets, update the batch timer
|
|
if (["batch_display", "batch_display_interval_ms", "batch_images_size"].includes(widget.name)) {
|
|
setupBatchInterval();
|
|
}
|
|
reinitBackground();
|
|
updateBackground();
|
|
};
|
|
}
|
|
});
|
|
|
|
// Function that returns the final image path, with different logic based on batchDisplayWidget
|
|
function getImagePath() {
|
|
const channel = channelWidget.value || "1";
|
|
const extension = "jpeg";
|
|
const folder = "preview_background";
|
|
let filename;
|
|
if (batchDisplayWidget && batchDisplayWidget.value) {
|
|
// Use batch display logic: include current batch index formatted为两位数
|
|
filename = `channel_${channel}_${String(batchIndex).padStart(2, '0')}.${extension}`;
|
|
} else {
|
|
filename = `channel_${channel}.${extension}`;
|
|
}
|
|
const basePath = window.location.href;
|
|
return `${basePath}view?filename=${filename}&subfolder=${folder}&type=output&rand=${Math.random()}`;
|
|
}
|
|
|
|
// Store the last loaded path to avoid reloading if not changed
|
|
let lastLoadedPath = null;
|
|
|
|
// Reset lastLoadedPath so the next poll triggers a reload
|
|
function reinitBackground() {
|
|
lastLoadedPath = null;
|
|
}
|
|
|
|
// Global interval timer for polling new images
|
|
let pollTimer = null;
|
|
function setupInterval() {
|
|
// Clear old timer if exists
|
|
if (pollTimer) {
|
|
clearInterval(pollTimer);
|
|
}
|
|
let ms = parseInt(intervalWidget.value || 300, 10);
|
|
pollTimer = setInterval(() => {
|
|
updateBackground();
|
|
}, ms);
|
|
}
|
|
|
|
// Global image object for background
|
|
if (!window._td_bg_img) {
|
|
window._td_bg_img = new Image();
|
|
window._td_bg_img.onload = () => {
|
|
// Trigger canvas redraw when image is loaded
|
|
if (app.canvas) {
|
|
app.canvas.draw(true, true);
|
|
}
|
|
};
|
|
}
|
|
|
|
// Variable to store the last image hash
|
|
let lastImageHash = null;
|
|
|
|
// Main function to update the background image
|
|
function updateBackground() {
|
|
if (!enableWidget.value) {
|
|
// If disabled, clear the image and trigger redraw
|
|
if (window._td_bg_img.src) {
|
|
window._td_bg_img.src = "";
|
|
lastLoadedPath = null;
|
|
lastImageHash = null;
|
|
}
|
|
return;
|
|
}
|
|
|
|
const path = getImagePath();
|
|
if (path !== lastLoadedPath) {
|
|
lastLoadedPath = path;
|
|
// Create a new image object to load the image
|
|
let imgObj = new Image();
|
|
imgObj.onload = async () => {
|
|
let base64 = convertImageToBase64(imgObj);
|
|
let newHash = await computeHash(base64);
|
|
// If the hash is unchanged from the last one, do not update the background
|
|
if (newHash === lastImageHash) {
|
|
return;
|
|
}
|
|
lastImageHash = newHash;
|
|
window._td_bg_img.src = base64;
|
|
};
|
|
imgObj.onerror = err => {
|
|
console.warn("Background image not found or load error:", path);
|
|
};
|
|
if (ENABLE_DEBUG) {
|
|
console.log("Loading background image:", path);
|
|
}
|
|
imgObj.src = path; // Prevent cache
|
|
}
|
|
}
|
|
|
|
// Initialize polling and update
|
|
setupInterval();
|
|
setupBatchInterval();
|
|
updateBackground();
|
|
}
|
|
|
|
// Handle new direct UI preview node
|
|
if (node.comfyClass === "VrchImagePreviewBackgroundNewNode") {
|
|
|
|
const enableWidget = node.widgets.find(w => w.name === "background_display");
|
|
const displayOptionWidget = node.widgets.find(w => w.name === "display_option");
|
|
const batchDisplayWidget = node.widgets.find(w => w.name === "batch_display");
|
|
const batchDisplayIntervalWidget = node.widgets.find(w => w.name === "batch_display_interval_ms");
|
|
|
|
const widgets = [enableWidget, displayOptionWidget,
|
|
batchDisplayWidget, batchDisplayIntervalWidget];
|
|
|
|
widgets.forEach(widget => {
|
|
if (widget) {
|
|
widget.callback = () => {
|
|
if (ENABLE_DEBUG) {
|
|
console.log(`callback:::${widget.name}:::${widget.value}`);
|
|
}
|
|
// For batch related widgets, update the batch timer
|
|
if (["batch_display", "batch_display_interval_ms"].includes(widget.name)) {
|
|
setupBatchInterval();
|
|
}
|
|
// For display option, reinitialize the background
|
|
if (widget.name === "display_option") {
|
|
window._td_bg_display_option = widget.value;
|
|
if (app.canvas) {
|
|
app.canvas.draw(true, true);
|
|
}
|
|
}
|
|
updateBackground();
|
|
};
|
|
}
|
|
});
|
|
|
|
// Global variable for batch index and timer
|
|
let batchIndex = 0;
|
|
let batchTimer = null;
|
|
// Function to set up the batch display timer if enabled
|
|
function setupBatchInterval() {
|
|
if (batchTimer) {
|
|
clearInterval(batchTimer);
|
|
batchTimer = null;
|
|
}
|
|
if (batchDisplayWidget && batchDisplayWidget.value) {
|
|
let ms = parseInt(batchDisplayIntervalWidget.value || 1000, 10);
|
|
let imageSize = window._td_bg_imgs.length; // Reset batchIndex to 0 when (re)starting timer
|
|
batchIndex = 0;
|
|
if (imageSize > 0) {
|
|
batchTimer = setInterval(() => {
|
|
batchIndex = (batchIndex + 1) % imageSize;
|
|
updateBackground();
|
|
if (ENABLE_DEBUG) {
|
|
console.log(`Batch display timer updated index to ${batchIndex}`);
|
|
}
|
|
}, ms);
|
|
}
|
|
}
|
|
}
|
|
|
|
function updateBackground() {
|
|
if (!enableWidget.value) {
|
|
// If disabled, clear the image and trigger redraw
|
|
if (window._td_bg_img.src) {
|
|
window._td_bg_img.src = "";
|
|
}
|
|
// Reset batch index
|
|
batchIndex = 0;
|
|
// Clear batch timer
|
|
if (batchTimer) {
|
|
clearInterval(batchTimer);
|
|
batchTimer = null;
|
|
}
|
|
return;
|
|
}
|
|
const base64 = window._td_bg_imgs?.[batchIndex];
|
|
if (base64) {
|
|
window._td_bg_img.src = base64;
|
|
}
|
|
}
|
|
|
|
// initialize enabled flag
|
|
window._td_bg_enabled = window._td_bg_enabled ?? false;
|
|
// Global image object for background
|
|
if (!window._td_bg_img) {
|
|
window._td_bg_img = new Image();
|
|
window._td_bg_img.onload = () => {
|
|
// Trigger canvas redraw when image is loaded
|
|
if (app.canvas) {
|
|
app.canvas.draw(true, true);
|
|
}
|
|
};
|
|
}
|
|
|
|
// Initialize polling and update
|
|
setupBatchInterval();
|
|
updateBackground();
|
|
|
|
// onExecuted receives images as base64 strings
|
|
node.onExecuted = function(message) {
|
|
window._td_bg_enabled = message.background_display?.[0] ?? false;
|
|
window._td_bg_imgs = message.background_images || [];
|
|
window._td_bg_display_option = message.display_option?.[0] || 'fit';
|
|
updateBackground();
|
|
};
|
|
}
|
|
},
|
|
|
|
init() {
|
|
// Overwrite LGraphCanvas drawBackCanvas for the entire app
|
|
const LGraphCanvas = window.LiteGraph.LGraphCanvas;
|
|
const originalDrawBack = LGraphCanvas.prototype.drawBackCanvas;
|
|
|
|
LGraphCanvas.prototype.drawBackCanvas = function(...args) {
|
|
// 1) Call original background drawing (grid, etc.)
|
|
originalDrawBack.apply(this, args);
|
|
|
|
// 2) If a background image is loaded, draw it with "fit" scaling
|
|
if (window._td_bg_img && window._td_bg_img.width) {
|
|
// Determine enable and display mode from old or new preview node
|
|
const nodes = app.graph._nodes.filter(n => ["VrchImagePreviewBackgroundNode","VrchImagePreviewBackgroundNewNode"].includes(n?.comfyClass));
|
|
let enableVal = false;
|
|
let displayOptionVal = 'fit';
|
|
const oldNode = nodes.find(n => n.comfyClass === 'VrchImagePreviewBackgroundNode');
|
|
const newNode = nodes.find(n => n.comfyClass === 'VrchImagePreviewBackgroundNewNode');
|
|
|
|
if (newNode) {
|
|
enableVal = window._td_bg_enabled;
|
|
displayOptionVal = window._td_bg_display_option || displayOptionVal;
|
|
} else if (oldNode) {
|
|
const eW = oldNode.widgets.find(w => w.name === 'background_display');
|
|
const dW = oldNode.widgets.find(w => w.name === 'display_option');
|
|
enableVal = eW? eW.value:false;
|
|
displayOptionVal = dW? dW.value:'fit';
|
|
}
|
|
|
|
if (!enableVal) return;
|
|
|
|
const ctx = this.bgcanvas.getContext("2d");
|
|
ctx.save();
|
|
|
|
// Reset transform if viewport is not defined
|
|
if (!this.viewport) {
|
|
ctx.setTransform(1, 0, 0, 1, 0, 0);
|
|
}
|
|
|
|
let offsetX, offsetY, drawWidth, drawHeight;
|
|
const canvasWidth = this.bgcanvas.width;
|
|
const canvasHeight = this.bgcanvas.height;
|
|
const imageWidth = window._td_bg_img.width;
|
|
const imageHeight = window._td_bg_img.height;
|
|
|
|
switch (displayOptionVal) {
|
|
case "original":
|
|
drawWidth = imageWidth;
|
|
drawHeight = imageHeight;
|
|
offsetX = (canvasWidth - imageWidth) / 2;
|
|
offsetY = (canvasHeight - imageHeight) / 2;
|
|
break;
|
|
case "fit": {
|
|
const scaleRatio = Math.min(canvasWidth / imageWidth, canvasHeight / imageHeight);
|
|
drawWidth = imageWidth * scaleRatio;
|
|
drawHeight = imageHeight * scaleRatio;
|
|
offsetX = (canvasWidth - drawWidth) / 2;
|
|
offsetY = (canvasHeight - drawHeight) / 2;
|
|
break;
|
|
}
|
|
case "stretch":
|
|
offsetX = 0;
|
|
offsetY = 0;
|
|
drawWidth = canvasWidth;
|
|
drawHeight = canvasHeight;
|
|
break;
|
|
case "crop": {
|
|
const scaleRatio = Math.max(canvasWidth / imageWidth, canvasHeight / imageHeight);
|
|
drawWidth = imageWidth * scaleRatio;
|
|
drawHeight = imageHeight * scaleRatio;
|
|
offsetX = (canvasWidth - drawWidth) / 2;
|
|
offsetY = (canvasHeight - drawHeight) / 2;
|
|
break;
|
|
}
|
|
default: {
|
|
const scaleRatio = Math.min(canvasWidth / imageWidth, canvasHeight / imageHeight);
|
|
drawWidth = imageWidth * scaleRatio;
|
|
drawHeight = imageHeight * scaleRatio;
|
|
offsetX = (canvasWidth - drawWidth) / 2;
|
|
offsetY = (canvasHeight - drawHeight) / 2;
|
|
}
|
|
}
|
|
ctx.drawImage(window._td_bg_img, offsetX, offsetY, drawWidth, drawHeight);
|
|
ctx.restore();
|
|
}
|
|
};
|
|
}
|
|
}); |