ref update

This commit is contained in:
JJ
2025-11-26 19:35:06 +00:00
parent 6240287439
commit ebda7b2a20
9 changed files with 2236 additions and 1037 deletions
+1
View File
@@ -32,6 +32,7 @@ from .nodes.cache_samples import *
# Import API endpoints to register routes
from . import api
from .utility.rgthree_api import routes_model_info
from .nodes import trigger_ref_refresh # Register PrepareRefs backend trigger endpoint
NODE_CONFIG = {}
+100 -9
View File
@@ -26,6 +26,17 @@ def get_bg_folder_path():
return str(bg_path)
def get_ref_folder_path():
"""Get the path to the ref folder where reference images are stored"""
import pathlib
current_dir = pathlib.Path(__file__).parent
ref_path = current_dir / "web" / "power_spline_editor" / "ref"
# Create the directory if it doesn't exist
ref_path.mkdir(parents=True, exist_ok=True)
return str(ref_path)
@PromptServer.instance.routes.post("/wanvideowrapper_qq/save_ref_image")
async def save_ref_image(request):
"""API endpoint to save ref_image to the bg folder"""
@@ -33,15 +44,15 @@ async def save_ref_image(request):
post = await request.post()
image_data = post.get("image")
image_name = post.get("name", "bg_image.png")
if not image_data:
return web.json_response({"error": "No image data provided"}, status=400)
# Derive filename safely
safe_name = os.path.basename(image_name) or "bg_image.png"
if "." not in safe_name:
safe_name += ".png"
# Decode base64 image data
if image_data.startswith('data:image'):
# Remove data URL prefix (e.g., "data:image/png;base64,")
@@ -50,10 +61,10 @@ async def save_ref_image(request):
else:
encoded = image_data
image_format = safe_name.split(".")[-1]
# Decode the base64 string
image_bytes = base64.b64decode(encoded)
# Verify it's a valid image
try:
img = Image.open(BytesIO(image_bytes))
@@ -61,11 +72,11 @@ async def save_ref_image(request):
img = Image.open(BytesIO(image_bytes)) # Reopen after verify
except Exception as e:
return web.json_response({"error": f"Invalid image data: {str(e)}"}, status=400)
# Get the bg folder path
bg_folder = get_bg_folder_path()
ref_image_path = os.path.join(bg_folder, safe_name)
# Decide format (prefer PNG to preserve alpha)
fmt = (image_format or "png").upper()
save_kwargs = {}
@@ -78,9 +89,9 @@ async def save_ref_image(request):
if img.mode not in ("RGBA", "LA"):
img = img.convert("RGBA")
fmt = "PNG"
img.save(ref_image_path, format=fmt, **save_kwargs)
return web.json_response({"success": True, "path": ref_image_path, "message": "Reference image saved successfully"})
except Exception as e:
@@ -89,6 +100,86 @@ async def save_ref_image(request):
traceback.print_exc()
return web.json_response({"error": str(e)}, status=500)
@PromptServer.instance.routes.post("/wanvideowrapper_qq/save_prepare_refs_images")
async def save_prepare_refs_images(request):
"""API endpoint to save ALL images from PrepareRefs node to the REF folder"""
try:
data = await request.json()
bg_image_data = data.get("bg_image")
ref_images_data = data.get("ref_images", [])
print(f"[API save_prepare_refs_images] === Received request ===")
print(f"[API save_prepare_refs_images] bg_image present: {bg_image_data is not None}")
print(f"[API save_prepare_refs_images] ref_images count: {len(ref_images_data) if isinstance(ref_images_data, list) else 0}")
saved_paths = {
"bg_image_path": None,
"ref_images_paths": []
}
# Get ref folder (ALL images go to ref folder for PrepareRefs!)
ref_folder = get_ref_folder_path()
print(f"[API save_prepare_refs_images] Using ref folder: {ref_folder}")
# Save background image to REF folder (not bg folder!)
if bg_image_data:
try:
print(f"[API save_prepare_refs_images] Saving bg_image to REF folder...")
# Decode base64 image data
if bg_image_data.startswith('data:image'):
header, encoded = bg_image_data.split(',', 1)
else:
encoded = bg_image_data
image_bytes = base64.b64decode(encoded)
img = Image.open(BytesIO(image_bytes))
# Save to REF folder as bg_image.png
bg_image_path = os.path.join(ref_folder, "bg_image.png")
img.save(bg_image_path, format="PNG")
saved_paths["bg_image_path"] = "ref/bg_image.png"
print(f"[API save_prepare_refs_images] ✓ Saved bg_image to {bg_image_path}")
except Exception as e:
print(f"[API save_prepare_refs_images] ERROR saving bg_image: {e}")
import traceback
traceback.print_exc()
# Save ref images to ref folder
if ref_images_data and isinstance(ref_images_data, list):
for idx, ref_image_data in enumerate(ref_images_data):
try:
print(f"[API save_prepare_refs_images] Saving ref_image {idx} to REF folder...")
# Decode base64 image data
if ref_image_data.startswith('data:image'):
header, encoded = ref_image_data.split(',', 1)
else:
encoded = ref_image_data
image_bytes = base64.b64decode(encoded)
img = Image.open(BytesIO(image_bytes))
# Save to ref folder
ref_image_path = os.path.join(ref_folder, f"ref_image_{idx}.png")
img.save(ref_image_path, format="PNG")
saved_paths["ref_images_paths"].append(f"ref/ref_image_{idx}.png")
print(f"[API save_prepare_refs_images] ✓ Saved ref_image_{idx} to {ref_image_path}")
except Exception as e:
print(f"[API save_prepare_refs_images] ERROR saving ref_image {idx}: {e}")
import traceback
traceback.print_exc()
print(f"[API save_prepare_refs_images] === COMPLETE ===")
print(f"[API save_prepare_refs_images] Saved bg_image: {saved_paths['bg_image_path']}")
print(f"[API save_prepare_refs_images] Saved {len(saved_paths['ref_images_paths'])} ref images")
return web.json_response({"success": True, "paths": saved_paths})
except Exception as e:
print(f"[API save_prepare_refs_images] ERROR: {e}")
import traceback
traceback.print_exc()
return web.json_response({"error": str(e)}, status=500)
def get_lora_full_path(lora_name):
"""Get the full path to a LoRA file"""
try:
+772 -485
View File
File diff suppressed because it is too large Load Diff
+131
View File
@@ -0,0 +1,131 @@
from server import PromptServer
from aiohttp import web
import base64
from io import BytesIO
from PIL import Image
import torch
import numpy as np
# Import PrepareRefs class
from .prepare_refs import PrepareRefs
def pil_to_tensor(pil_img):
"""Convert PIL image to tensor [1, H, W, C] in range [0, 1]"""
img_np = np.array(pil_img).astype(np.float32) / 255.0
if len(img_np.shape) == 2: # Grayscale
img_np = np.stack([img_np] * 3, axis=-1)
return torch.from_numpy(img_np)
@PromptServer.instance.routes.post("/wanvideowrapper_qq/trigger_prepare_refs")
async def trigger_prepare_refs(request):
"""
Backend trigger to run PrepareRefs processing without node execution.
Input JSON:
{
"bg_image": "data:image/png;base64,...",
"ref_layer_data": [{name, on, lassoShape: {additivePaths}}],
"mask_width": 640,
"mask_height": 480
}
Note: export_alpha and to_bounding_box are always True (hardcoded in PrepareRefs).
All ref images are exported as 768x768 PNG with alpha channel.
Output JSON:
{
"success": true/false,
"paths": {
"bg_image_cl": "ref/bg_image_cl.png",
"bg_preview": "bg/bg_image.png",
"ref_images": ["ref/ref_1.png", ...],
"ref_masks": ["ref/ref_1_mask.png", ...]
},
"message": "PrepareRefs processing complete: N layers processed",
"error": "error message if failed"
}
"""
try:
data = await request.json()
# Extract parameters
bg_image_b64 = data.get("bg_image")
ref_layer_data = data.get("ref_layer_data", [])
mask_width = data.get("mask_width", 640)
mask_height = data.get("mask_height", 480)
print(f"[trigger_prepare_refs] Received request with {len(ref_layer_data)} layers, dims: {mask_width}x{mask_height}")
# Validate layer data
if not ref_layer_data or len(ref_layer_data) == 0:
return web.json_response({
"success": False,
"error": "No ref_layer_data provided"
}, status=400)
# Decode bg_image to tensor
bg_tensor = None
if bg_image_b64:
# Remove data URL prefix if present
if bg_image_b64.startswith('data:image'):
bg_image_b64 = bg_image_b64.split(',', 1)[1]
image_bytes = base64.b64decode(bg_image_b64)
pil_img = Image.open(BytesIO(image_bytes))
# Convert to RGB if needed
if pil_img.mode != 'RGB':
pil_img = pil_img.convert('RGB')
print(f"[trigger_prepare_refs] Decoded bg_image: {pil_img.size}, mode: {pil_img.mode}")
# Convert to tensor [1, H, W, C]
bg_tensor = pil_to_tensor(pil_img).unsqueeze(0)
# Construct prompt structure (PrepareRefs expects this format)
prompt = {
"unique_id": {
"inputs": {
"ref_layer_data": ref_layer_data
}
}
}
# Create PrepareRefs instance and execute
print(f"[trigger_prepare_refs] Calling PrepareRefs.prepare()...")
prep_refs = PrepareRefs()
result = prep_refs.prepare(
mask_width=mask_width,
mask_height=mask_height,
bg_image=bg_tensor,
unique_id="unique_id", # Key used in prompt dict
prompt=prompt
)
# Extract paths from result
ui_data = result.get("ui", {})
paths = {
"bg_image_cl": ui_data.get("bg_image_path", [None])[0],
"bg_preview": ui_data.get("bg_image_preview_path", [None])[0],
"ref_images": ui_data.get("ref_images_paths", []),
"ref_masks": ui_data.get("ref_masks_paths", [])
}
print(f"[trigger_prepare_refs] Processing complete. Generated paths: {paths}")
return web.json_response({
"success": True,
"paths": paths,
"message": f"PrepareRefs processing complete: {len(ref_layer_data)} layers processed"
})
except Exception as e:
print(f"[trigger_prepare_refs] ERROR: {e}")
import traceback
traceback.print_exc()
return web.json_response({
"success": False,
"error": str(e)
}, status=500)
+6
View File
@@ -494,6 +494,9 @@ function enterLassoMode(node, refLayer) {
canvas.style.cursor = 'crosshair';
}
// Update button states to show green pen for active layer
node.updateLayerButtonStates?.();
console.log(`Lasso mode activated for ${refLayer.value.name}`);
}
@@ -518,6 +521,9 @@ function exitLassoMode(node) {
canvas.style.cursor = 'default';
}
// Update button states to reset pen colors
node.updateLayerButtonStates?.();
console.log('Lasso mode deactivated');
}
+204 -17
View File
@@ -4,6 +4,7 @@ import {
drawNumberWidgetPart,
RgthreeBaseWidget
} from './drawing_utils.js';
import { triggerPrepareRefsBackend } from './trigger_ref_refresh.js';
// === TOP ROW WIDGET (Combined refresh button, bg_image dropdown, and width/height controls) ===
export class TopRowWidget extends RgthreeBaseWidget {
@@ -268,28 +269,65 @@ export class TopRowWidget extends RgthreeBaseWidget {
// Set up event handlers for refresh button
if (this.visibility.refreshCanvasButton) {
this.hitAreas.refreshCanvasButton.onClick = async () => {
// Update all box layer ref images from connected ref_images
if (node.updateAllBoxLayerRefs) {
try {
await node.updateAllBoxLayerRefs();
} catch (e) {
console.warn('Failed to update box layer refs:', e);
// Check if ref_images input is connected to PrepareRefs
let isPrepareRefsConnected = false;
let prepareRefsNode = null;
try {
// Import the graph query function at the top of the file
const { findConnectedSourceNode } = await import('./graph_query.js');
const sourceNodeObj = findConnectedSourceNode(node, 'ref_images');
if (sourceNodeObj && sourceNodeObj.node) {
if (sourceNodeObj.node.type === 'PrepareRefs') {
console.log('[Refresh] PowerSplineEditor connected to PrepareRefs');
isPrepareRefsConnected = true;
prepareRefsNode = sourceNodeObj.node;
}
}
} catch (e) {
console.warn('Failed to check ref_images connection:', e);
}
// Wait for reference image update and scale recalculation to complete
if (node.updateReferenceImageFromConnectedNode) {
try {
// This now properly awaits the image loading and scale recalculation
await node.updateReferenceImageFromConnectedNode();
if (isPrepareRefsConnected && prepareRefsNode) {
// Trigger backend PrepareRefs processing
console.log('[Refresh] Triggering backend PrepareRefs processing...');
const result = await triggerPrepareRefsBackend(prepareRefsNode);
// Force a final render with the correct scale
// The scale should already be correct from handleImageLoad -> recenterBackgroundImage
if (node.editor && node.editor.layerRenderer) {
node.editor.layerRenderer.render();
if (result.success) {
console.log('[Refresh] Backend processing complete, loading results...');
// Load the newly generated images from ref folder
await this.loadImagesFromRefFolder(node);
console.log('[Refresh] Canvas refresh complete');
} else {
// Silent error handling - log only, no user interruption
console.error('[Refresh] Backend processing failed:', result.error);
}
} else {
// Original behavior: fetch from connected nodes
// Update all box layer ref images from connected ref_images
if (node.updateAllBoxLayerRefs) {
try {
await node.updateAllBoxLayerRefs();
} catch (e) {
console.warn('Failed to update box layer refs:', e);
}
}
// Wait for reference image update and scale recalculation to complete
if (node.updateReferenceImageFromConnectedNode) {
try {
// This now properly awaits the image loading and scale recalculation
await node.updateReferenceImageFromConnectedNode();
// Force a final render with the correct scale
// The scale should already be correct from handleImageLoad -> recenterBackgroundImage
if (node.editor && node.editor.layerRenderer) {
node.editor.layerRenderer.render();
}
} catch (e) {
console.warn('Failed to update reference image:', e);
}
} catch (e) {
console.warn('Failed to update reference image:', e);
}
}
@@ -435,6 +473,155 @@ export class TopRowWidget extends RgthreeBaseWidget {
}
}
// Load images from ref folder when PrepareRefs is connected
async loadImagesFromRefFolder(node) {
const { loadImageAsBase64 } = await import('./graph_query.js');
const timestamp = Date.now();
// 1. Load bg_image_cl.png from ref folder
try {
const bgImageUrl = new URL(`ref/bg_image_cl.png?t=${timestamp}`, import.meta.url).href;
const bgImageData = await loadImageAsBase64(bgImageUrl);
if (bgImageData) {
// Store the background image
node.originalRefImageData = {
name: 'bg_image_cl.png',
base64: bgImageData.split(',')[1],
type: 'image/png'
};
// Update background image display based on current bg_img selection
const bgImgWidget = node.widgets?.find(w => w.name === "bg_img");
const bg_img = bgImgWidget ? bgImgWidget.value : "None";
await node.updateBackgroundImage(bg_img);
console.log('Successfully loaded bg_image_cl.png from ref folder');
}
} catch (e) {
console.warn('Failed to load bg_image_cl.png from ref folder:', e);
}
// 2. Find and load all ref_*.png files from ref folder
const refImages = [];
const maxRefs = 5; // Try to load up to 5 reference images
for (let i = 1; i <= maxRefs; i++) {
try {
const refImageUrl = new URL(`ref/ref_${i}.png?t=${timestamp}`, import.meta.url).href;
const refImageData = await loadImageAsBase64(refImageUrl);
if (refImageData) {
refImages.push(refImageData);
console.log(`Successfully loaded ref_${i}.png from ref folder`);
} else {
// Stop trying if we can't load this image
break;
}
} catch (e) {
// Stop trying if we encounter an error (file doesn't exist)
console.log(`No more ref images found after ref_${i - 1}.png`);
break;
}
}
// 3. Update box layer refs with loaded images
if (refImages.length > 0) {
const activeWidget = node.layerManager?.getActiveWidget?.();
if (!activeWidget || activeWidget.value?.type !== 'box_layer') {
console.log('No active box layer to attach ref images');
// Try to update all box layers instead
await this.updateAllBoxLayersFromRefFolder(node, refImages);
} else {
// Update active box layer
await this.updateBoxLayerWithRefImages(node, activeWidget, refImages);
}
}
// Force a final render
if (node.editor && node.editor.layerRenderer) {
node.editor.layerRenderer.render();
}
}
// Update a specific box layer with ref images from folder
async updateBoxLayerWithRefImages(node, boxWidget, refImages) {
const attachments = [];
for (let i = 0; i < refImages.length; i++) {
const imgData = refImages[i];
const base64Data = imgData.startsWith('data:')
? imgData.split(',')[1]
: imgData;
const dataUrl = imgData.startsWith('data:')
? imgData
: `data:image/png;base64,${base64Data}`;
// Load dimensions
const dims = await new Promise((resolve) => {
const img = new Image();
img.onload = () => resolve({ width: img.width, height: img.height });
img.onerror = () => resolve({ width: 1, height: 1 });
img.src = dataUrl;
});
const filename = `ref_${i + 1}.png`;
attachments.push({
path: `ref/${filename}`,
type: 'image/png',
width: dims.width,
height: dims.height,
name: filename
});
}
boxWidget.value.ref_attachment = { entries: attachments };
// Preserve existing ref_selection if valid, otherwise default to first ref
const currentSelection = boxWidget.value.ref_selection;
if (attachments.length > 0) {
// Check if current selection is still valid
if (currentSelection && currentSelection !== 'no_ref') {
const refIndex = parseInt(currentSelection.split('_')[1], 10);
if (refIndex > 0 && refIndex <= attachments.length) {
// Keep the current selection as it's still valid
boxWidget.value.ref_selection = currentSelection;
} else {
// Current selection is out of range, default to ref_1
boxWidget.value.ref_selection = 'ref_1';
}
} else {
// No current selection or it was 'no_ref', default to ref_1
boxWidget.value.ref_selection = 'ref_1';
}
} else {
boxWidget.value.ref_selection = 'no_ref';
}
// Clear ref image cache to force reload
if (node.editor?.layerRenderer?.clearRefImageCache) {
node.editor.layerRenderer.clearRefImageCache();
}
node.setDirtyCanvas(true, true);
}
// Update all box layers with ref images from folder
async updateAllBoxLayersFromRefFolder(node, refImages) {
const widgets = node.layerManager?.getSplineWidgets?.() || [];
const boxWidgets = widgets.filter(w => w?.value?.type === 'box_layer');
if (!boxWidgets.length) {
console.log('No box layers found to update');
return;
}
for (const boxWidget of boxWidgets) {
await this.updateBoxLayerWithRefImages(node, boxWidget, refImages);
}
console.log(`Updated ${boxWidgets.length} box layer(s) with ref images from folder`);
}
// Mouse event handlers
mouse(event, pos, node) {
+333 -23
View File
@@ -483,21 +483,92 @@ app.registerExtension({
const bgImgWidget = this.widgets && this.widgets.find(w => w.name === "bg_img");
const bg_img = bgImgWidget ? bgImgWidget.value : "None";
// Try to pull a fresh ref image from connected node silently
const base64Image = await getReferenceImageFromConnectedNode(this);
if (base64Image) {
this.originalRefImageData = {
name: 'ref_image_from_connection.jpg',
base64: base64Image.split(',')[1],
type: 'image/jpeg'
};
try { await saveRefImageToCache(this.originalRefImageData.base64, 'bg_image.png'); } catch {}
if (this.uuid) {
sessionStorage.removeItem(`spline-editor-img-${this.uuid}`);
// Check if connected to PrepareRefs - if so, load bg_image_cl.png from ref folder instead
const isConnectedToPrepareRefs = this.checkIfConnectedToPrepareRefs();
if (isConnectedToPrepareRefs) {
console.log('Connected to PrepareRefs, loading bg_image_cl.png from ref folder');
try {
const timestamp = Date.now();
const refImageUrl = new URL(`ref/bg_image_cl.png?t=${timestamp}`, import.meta.url).href;
const response = await fetch(refImageUrl);
if (response.ok) {
const blob = await response.blob();
const base64Data = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result.split(',')[1]);
reader.onerror = reject;
reader.readAsDataURL(blob);
});
this.originalRefImageData = {
name: 'bg_image_cl.png',
base64: base64Data,
type: 'image/png'
};
// Process the image immediately based on bg_img selection
if (bg_img === "None") {
// Apply darkening for "None" selection
const img = new Image();
img.onload = () => {
const canvas = document.createElement('canvas');
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
ctx.fillStyle = 'rgba(0, 0, 0, 0.6)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
const darkenedDataUrl = canvas.toDataURL('image/png');
this.imgData = {
name: 'bg_image_cl.png',
base64: darkenedDataUrl.split(',')[1],
type: 'image/png'
};
if (this.editor) {
this.editor.refreshBackgroundImage();
}
};
img.src = `data:image/png;base64,${base64Data}`;
} else {
// For A/B/C selections, set imgData directly for now
this.imgData = {
name: 'bg_image_cl.png',
base64: base64Data,
type: 'image/png'
};
if (this.editor) {
this.editor.refreshBackgroundImage();
}
// Also trigger updateBackgroundImage for proper overlay handling
this.updateBackgroundImage(bg_img);
}
if (this.uuid) {
sessionStorage.removeItem(`spline-editor-img-${this.uuid}`);
}
return;
}
} catch (e) {
console.error('Failed to load bg_image_cl.png from ref folder:', e);
}
} else {
// Try to pull a fresh ref image from connected node silently
const base64Image = await getReferenceImageFromConnectedNode(this);
if (base64Image) {
this.originalRefImageData = {
name: 'ref_image_from_connection.jpg',
base64: base64Image.split(',')[1],
type: 'image/jpeg'
};
try { await saveRefImageToCache(this.originalRefImageData.base64, 'bg_image.png'); } catch {}
if (this.uuid) {
sessionStorage.removeItem(`spline-editor-img-${this.uuid}`);
}
this.updateBackgroundImage(bg_img);
this.editor?.refreshBackgroundImage?.();
return;
}
this.updateBackgroundImage(bg_img);
this.editor?.refreshBackgroundImage?.();
return;
}
} catch {}
// Fallback: just apply current selection to force overlay/cached handling
@@ -617,6 +688,35 @@ app.registerExtension({
// Set initial node size
this.updateNodeHeight();
// Helper method to load the correct cached ref image based on PrepareRefs connection
this.loadCorrectCachedRefImage = async function() {
const isConnectedToPrepareRefs = this.checkIfConnectedToPrepareRefs();
if (isConnectedToPrepareRefs) {
// Load bg_image_cl.png from ref folder
try {
const timestamp = Date.now();
const refImageUrl = new URL(`ref/bg_image_cl.png?t=${timestamp}`, import.meta.url).href;
const response = await fetch(refImageUrl);
if (response.ok) {
const blob = await response.blob();
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.onerror = reject;
reader.readAsDataURL(blob);
});
}
} catch (e) {
console.error('Failed to load bg_image_cl.png from ref folder:', e);
}
return null;
} else {
// Load bg_image.png from bg folder (standard behavior)
return await loadCachedRefImageAsBase64();
}
}.bind(this);
// Add method to update background image immediately based on bg_img selection
this.updateBackgroundImage = async function(bg_img) {
// First check if we have saved dimensions to maintain proportions
@@ -661,7 +761,7 @@ app.registerExtension({
};
img.onerror = () => {
// Fallback: try to load cached bg_image first, then default A.jpg
loadCachedRefImageAsBase64().then(cachedImageUrl => {
this.loadCorrectCachedRefImage().then(cachedImageUrl => {
if (cachedImageUrl) {
const fallbackImg = new Image();
fallbackImg.onload = () => {
@@ -717,7 +817,7 @@ app.registerExtension({
img.src = `data:image/jpeg;base64,${this.originalRefImageData.base64}`;
} else {
// If no original ref image data, try to load cached bg_image first, then fallback to default
loadCachedRefImageAsBase64().then(cachedImageUrl => {
this.loadCorrectCachedRefImage().then(cachedImageUrl => {
if (cachedImageUrl) {
const img = new Image();
img.onload = () => {
@@ -781,10 +881,11 @@ app.registerExtension({
if (this.originalRefImageData && this.originalRefImageData.base64) {
// Use original ref image if available (from refresh button)
refImageForOverlay = `data:image/jpeg;base64,${this.originalRefImageData.base64}`;
const imgType = this.originalRefImageData.type || 'image/jpeg';
refImageForOverlay = `data:${imgType};base64,${this.originalRefImageData.base64}`;
} else {
// Otherwise, load from cached
loadCachedRefImageAsBase64().then(cachedImageUrl => {
this.loadCorrectCachedRefImage().then(cachedImageUrl => {
if (cachedImageUrl) {
// Create a properly scaled overlay using the original cached ref image
this.createScaledImageOverlay(cachedImageUrl, bg_img, imageUrl);
@@ -815,6 +916,58 @@ app.registerExtension({
console.log('Attempting to update reference image from connected node...');
try {
// Check if we're connected to PrepareRefs node
const isConnectedToPrepareRefs = this.checkIfConnectedToPrepareRefs();
if (isConnectedToPrepareRefs) {
console.log('Connected to PrepareRefs node, loading bg_image_cl.png from ref folder');
try {
const timestamp = Date.now();
const refImageUrl = new URL(`ref/bg_image_cl.png?t=${timestamp}`, import.meta.url).href;
const response = await fetch(refImageUrl);
if (!response.ok) {
console.error('Failed to load bg_image_cl.png from ref folder');
alert('Could not load bg_image_cl.png from ref folder. Make sure the file exists.');
return;
}
const blob = await response.blob();
const base64Data = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result.split(',')[1]);
reader.onerror = reject;
reader.readAsDataURL(blob);
});
this.originalRefImageData = {
name: 'bg_image_cl.png',
base64: base64Data,
type: 'image/png'
};
// Clear session storage cache for this node's image to force refresh
if (this.uuid) {
sessionStorage.removeItem(`spline-editor-img-${this.uuid}`);
}
// Get current bg_img selection to update background accordingly
const bgImgWidget = this.widgets.find(w => w.name === "bg_img");
const bg_img = bgImgWidget ? bgImgWidget.value : "None";
// Update background based on current bg_img selection
this.updateBackgroundImage(bg_img);
// Wait for the image processing to complete
await new Promise(resolve => setTimeout(resolve, 200));
console.log('Successfully loaded bg_image_cl.png from ref folder');
} catch (error) {
console.error('Error loading bg_image_cl.png:', error);
alert('Error loading bg_image_cl.png: ' + error.message);
}
return;
}
// Get reference image from connected bg_image input
const base64Image = await getReferenceImageFromConnectedNode(this, 'bg_image');
if (!base64Image) {
@@ -864,6 +1017,15 @@ app.registerExtension({
return;
}
// Check if we're connected to PrepareRefs node
const isConnectedToPrepareRefs = this.checkIfConnectedToPrepareRefs();
if (isConnectedToPrepareRefs) {
// When connected to PrepareRefs, load ref images from ref folder instead
await this.loadRefImagesFromRefFolder(activeWidget, desiredSelection);
return;
}
// Fetch images from ref_images input (first frame used)
const images = await getReferenceImagesFromConnectedNode(this);
if (!images || images.length === 0) {
@@ -980,6 +1142,17 @@ app.registerExtension({
this.layerRenderer.clearRefImageCache();
}
// Check if we're connected to PrepareRefs node
const isConnectedToPrepareRefs = this.checkIfConnectedToPrepareRefs();
if (isConnectedToPrepareRefs) {
// When connected to PrepareRefs, load ref images from ref folder for all box layers
for (const boxWidget of boxWidgets) {
await this.loadRefImagesFromRefFolder(boxWidget, boxWidget.value.ref_selection || 'ref_1');
}
return;
}
// Fetch images from ref_images input (first frame used)
const images = await getReferenceImagesFromConnectedNode(this);
if (!images || images.length === 0) {
@@ -1372,6 +1545,142 @@ app.registerExtension({
}
}.bind(this);
// Helper method to check if connected to PrepareRefs node
this.checkIfConnectedToPrepareRefs = function() {
// Check if any of our inputs is connected to a PrepareRefs node
const graph = app.graph;
if (!graph || !graph.links || !this.inputs) {
return false;
}
for (let i = 0; i < this.inputs.length; i++) {
const input = this.inputs[i];
if (!input || !input.name) continue;
// Look for connections to this input
let link = null;
if (graph.links instanceof Map) {
for (const [linkId, linkObj] of graph.links) {
if (linkObj && linkObj.target_id === this.id && linkObj.target_slot === i) {
link = linkObj;
break;
}
}
} else if (Array.isArray(graph.links)) {
link = graph.links.find(linkObj =>
linkObj && linkObj.target_id === this.id && linkObj.target_slot === i
);
}
if (link) {
const sourceNode = graph._nodes?.find(n => n.id === link.origin_id);
if (sourceNode && sourceNode.type === 'PrepareRefs') {
return true;
}
}
}
return false;
}.bind(this);
// Helper method to load ref images from ref folder
this.loadRefImagesFromRefFolder = async function(boxWidget, desiredSelection = 'ref_1') {
try {
// Load ALL available ref images (ref_1.png through ref_5.png) from ref folder
const attachments = [];
const maxRefs = 5;
const timestamp = Date.now();
for (let i = 1; i <= maxRefs; i++) {
try {
const refImageUrl = new URL(`ref/ref_${i}.png?t=${timestamp}`, import.meta.url).href;
const response = await fetch(refImageUrl);
if (!response.ok) {
console.log(`ref_${i}.png not found in ref folder, skipping`);
continue;
}
const blob = await response.blob();
const base64Data = await new Promise((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(reader.result.split(',')[1]);
reader.onerror = reject;
reader.readAsDataURL(blob);
});
// Load dimensions to fit box without stretching
const dims = await new Promise((resolve) => {
const img = new Image();
img.onload = () => resolve({ width: img.width, height: img.height });
img.onerror = () => resolve({ width: 1, height: 1 });
img.src = `data:image/png;base64,${base64Data}`;
});
// Save image to disk and store path
const filename = `ref_${i}.png`;
await saveRefImageToCache(base64Data, filename);
// Create attachment
attachments.push({
path: `ref/${filename}`,
type: 'image/png',
width: dims.width,
height: dims.height,
name: filename
});
console.log(`Successfully loaded ref_${i}.png from ref folder`);
} catch (error) {
console.error(`Error loading ref_${i}.png:`, error);
}
}
if (attachments.length === 0) {
console.error('No ref images found in ref folder');
return;
}
// Set all attachments on the box widget
boxWidget.value.ref_attachment = { entries: attachments };
// Set the desired selection if valid, otherwise set to first available
const availableOptions = boxWidget._getRefOptions ? boxWidget._getRefOptions() : ['no_ref', 'ref_1', 'ref_2', 'ref_3', 'ref_4', 'ref_5'];
const desiredIdx = desiredSelection && availableOptions.includes(desiredSelection)
? Math.max(0, (parseInt(desiredSelection.split('_')[1], 10) || 1) - 1)
: 0;
if (desiredIdx >= attachments.length) {
boxWidget.value.ref_selection = 'no_ref';
} else {
const clampedIndex = Math.min(attachments.length - 1, desiredIdx);
boxWidget.value.ref_selection = `ref_${clampedIndex + 1}`;
}
// Persist to sessionStorage
try {
const keyId = this.id ?? this.uuid;
const key = keyId ? `spline-editor-boxref-${keyId}-${boxWidget.value.name || boxWidget.name || 'box'}` : null;
if (key) {
sessionStorage.setItem(key, JSON.stringify({
attachment: boxWidget.value.ref_attachment,
selection: boxWidget.value.ref_selection,
}));
}
} catch (e) {
console.warn('Failed to persist box ref attachment to session:', e);
}
// Clear ref image cache to force reload
if (this.layerRenderer && this.layerRenderer.clearRefImageCache) {
this.layerRenderer.clearRefImageCache();
}
console.log(`Successfully loaded ${attachments.length} ref images from ref folder for box layer`);
} catch (error) {
console.error('Error loading ref images from ref folder:', error);
}
}.bind(this);
// context menu
this.contextMenu = document.createElement("div");
this.contextMenu.className = 'spline-editor-context-menu';
@@ -1488,9 +1797,10 @@ app.registerExtension({
}
// Try to load cached bg_image first
loadCachedRefImageAsBase64().then(cachedImageUrl => {
this.loadCorrectCachedRefImage().then(cachedImageUrl => {
if (cachedImageUrl) {
this.loadBackgroundImageFromUrl(cachedImageUrl, 'bg_image.png', targetWidth, targetHeight);
const filename = this.checkIfConnectedToPrepareRefs() ? 'bg_image_cl.png' : 'bg_image.png';
this.loadBackgroundImageFromUrl(cachedImageUrl, filename, targetWidth, targetHeight);
} else {
// Fallback to default A.jpg
const timestamp = Date.now();
@@ -1685,7 +1995,7 @@ app.registerExtension({
// Determine which image to load based on the bg_img selection
if (bg_img === "None") {
// Try to load cached bg_image first, then fallback to default
loadCachedRefImageAsBase64().then(cachedImageUrl => {
this.loadCorrectCachedRefImage().then(cachedImageUrl => {
if (cachedImageUrl) {
// Apply darkening effect to match onExecuted behavior
const img = new Image();
@@ -1726,9 +2036,9 @@ app.registerExtension({
// Load the selected background image (A, B, or C) from the bg folder
const timestamp = Date.now();
const imageUrl = new URL(`bg/${bg_img}.jpg?t=${timestamp}`, import.meta.url).href;
// Use the overlay function from image_overlay.js
loadCachedRefImageAsBase64().then(cachedImageUrl => {
this.loadCorrectCachedRefImage().then(cachedImageUrl => {
if (cachedImageUrl) {
createImageOverlayForConfigure(
cachedImageUrl,
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,99 @@
/**
* Frontend trigger for PrepareRefs backend processing
*
* This module provides functionality to trigger PrepareRefs processing
* from the frontend without running the full ComfyUI workflow.
*/
import { getReferenceImageFromConnectedNode } from './graph_query.js';
/**
* Trigger PrepareRefs processing in the backend
*
* Sends lasso layer data and bg_image to backend for processing.
* All ref images are exported as 768x768 PNG with alpha channel.
*
* @param {Object} node - The PrepareRefs node instance
* @returns {Promise<Object>} Response with success status and file paths
*/
export async function triggerPrepareRefsBackend(node) {
try {
console.log('[triggerPrepareRefsBackend] Starting backend trigger...');
// 1. Get current bg_image from connected node or canvas
let bgImageBase64 = null;
// Try to get from connected node first
bgImageBase64 = await getReferenceImageFromConnectedNode(node, 'bg_image');
// Fallback: get from canvas if available
if (!bgImageBase64 && node.refCanvasEditor?.backgroundImage) {
const canvas = document.createElement('canvas');
const img = node.refCanvasEditor.backgroundImage;
canvas.width = img.width;
canvas.height = img.height;
const ctx = canvas.getContext('2d');
ctx.drawImage(img, 0, 0);
bgImageBase64 = canvas.toDataURL('image/png');
console.log('[triggerPrepareRefsBackend] Using bg_image from canvas');
}
if (!bgImageBase64) {
console.warn('[triggerPrepareRefsBackend] No bg_image found');
return { success: false, error: 'No background image available' };
}
// 2. Get ref_layer_data from node
const refLayerData = node.getRefLayerData?.() || [];
if (refLayerData.length === 0) {
console.warn('[triggerPrepareRefsBackend] No ref layers with shapes found');
return { success: false, error: 'No ref layers with shapes to process' };
}
console.log('[triggerPrepareRefsBackend] Found', refLayerData.length, 'layers with shapes');
// 3. Get dimensions
const widthWidget = node.widgets?.find(w => w.name === 'mask_width');
const heightWidget = node.widgets?.find(w => w.name === 'mask_height');
const maskWidth = widthWidget?.value || 640;
const maskHeight = heightWidget?.value || 480;
console.log('[triggerPrepareRefsBackend] Dimensions:', maskWidth, 'x', maskHeight);
// 4. Prepare payload
const payload = {
bg_image: bgImageBase64,
ref_layer_data: refLayerData,
mask_width: maskWidth,
mask_height: maskHeight
};
console.log('[triggerPrepareRefsBackend] Sending request with', refLayerData.length, 'layers');
// 5. Call backend endpoint
const response = await fetch('/wanvideowrapper_qq/trigger_prepare_refs', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
});
const result = await response.json();
if (!result.success) {
throw new Error(result.error || 'Backend processing failed');
}
console.log('[triggerPrepareRefsBackend] Success:', result.message);
console.log('[triggerPrepareRefsBackend] Generated files:', result.paths);
return result;
} catch (error) {
console.error('[triggerPrepareRefsBackend] Error:', error);
return {
success: false,
error: error.message || 'Unknown error during backend trigger'
};
}
}