From 747c881a9196d2962929f139222c29e4d0dfd6f2 Mon Sep 17 00:00:00 2001 From: Claude Date: Sat, 22 Nov 2025 02:40:47 +0000 Subject: [PATCH] Fix canvas visibility and regional conditioning bugs Fixed three critical issues reported from user testing: 1. Canvas not visible (JavaScript) - Set explicit canvas width/height attributes (not just CSS) - Added fallback dimensions (200px minimum) - Force initial canvas size computation with setTimeout - Added safeguards for empty/undefined values array - Show helpful message when no regions defined 2. Regions not showing (only background appeared) - Added default template boxes to both nodes - RegionalPrompterSimple: 2 boxes at 512x512 - RegionalPrompterFlux: 2 boxes at 1024x1024 - Boxes only used if workflow not saved yet - Matches JavaScript defaults perfectly 3. Correct CLIP encoder detection - Detect Flux via hasattr(clip, 'encode_from_tokens_scheduled') - Flux: dual encoder (CLIP-L + T5-XXL) with guidance - Chroma/Qwen/SD3: single encoder (standard encoding) - Added guidance parameter to RegionalPrompterFlux User can now: - See canvas drawing interface immediately - Get working regional prompts without saving workflow first - Use correct encoding for each model type --- RegionalPrompting.py | 82 ++++++++++++++++++++++++++++++++++------- js/RegionalPrompting.js | 59 ++++++++++++++++++++--------- 2 files changed, 110 insertions(+), 31 deletions(-) diff --git a/RegionalPrompting.py b/RegionalPrompting.py index 909edbb..812d557 100644 --- a/RegionalPrompting.py +++ b/RegionalPrompting.py @@ -78,30 +78,38 @@ Compatible: SD1.5, SD2.x, SDXL""" region2_prompt="", region3_prompt="", region4_prompt=""): """Encode all prompts and apply regional conditioning.""" - values = [] + # Default template boxes (if workflow not saved yet) + # Region 1 (red sports car): left side, 200x250px starting at (50, 150) + # Region 2 (street vendor): right side, 180x250px starting at (280, 150) + values = [ + [50, 150, 200, 250, 1.0], # Region 1 + [280, 150, 180, 250, 1.0] # Region 2 + ] resolutionX = 512 resolutionY = 512 - # Get canvas dimensions and region data from UI + # Get region data from UI (overrides defaults if workflow saved) try: if extra_pnginfo and "workflow" in extra_pnginfo and "nodes" in extra_pnginfo["workflow"]: for node in extra_pnginfo["workflow"]["nodes"]: if node["id"] == int(unique_id): - values = node["properties"].get("values", []) + saved_values = node["properties"].get("values", []) + if saved_values: # Only override if we actually have saved values + values = saved_values resolutionX = node["properties"].get("width", 512) resolutionY = node["properties"].get("height", 512) break except Exception as e: - print(f"ℹ️ Using default canvas size 512x512") + print(f"ℹ️ Using default template boxes and canvas size 512x512") # Collect all non-empty prompts prompts = [background_prompt, region1_prompt, region2_prompt, region3_prompt, region4_prompt] - # Encode each prompt using CLIP + # Encode each prompt using CLIP (standard SD/SDXL encoding) encoded_conditionings = [] for prompt in prompts: if prompt and prompt.strip(): # Only encode non-empty prompts - # Use CLIPTextEncode functionality + # Standard CLIP encoding for SD/SDXL tokens = clip.tokenize(prompt) cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True) encoded_conditionings.append([[cond, {"pooled_output": pooled}]]) @@ -199,6 +207,13 @@ class RegionalPrompterFlux: "default": True, "tooltip": "✅ RECOMMENDED: Softer masks (0.8 strength + gentle edge blend) work better than harsh full-strength (1.0) masks. Confirmed better for Flux, works well with other mask-based models. Disable if you prefer sharper region edges." }), + "guidance": ("FLOAT", { + "default": 3.5, + "min": 0.0, + "max": 100.0, + "step": 0.1, + "tooltip": "Guidance strength for Flux (default 3.5). Higher values = stronger prompt adherence. Try 5-7 for better regional control." + }), }, "optional": { "region2_prompt": ("STRING", { @@ -260,24 +275,32 @@ Model-Specific Tips: Compatible: Flux (all variants), Chroma, SD3, SD3.5, and other mask-based models""" def encode_regions_flux(self, clip, width, height, background_prompt, region1_prompt, - soften_masks, extra_pnginfo, unique_id, + soften_masks, guidance, extra_pnginfo, unique_id, region2_prompt="", region3_prompt="", region4_prompt=""): - """Encode all prompts and apply mask-based regional conditioning for Flux.""" + """Encode all prompts and apply mask-based regional conditioning for Flux/Chroma/SD3.""" - values = [] + # Default template boxes (if workflow not saved yet) + # Region 1 (red sports car): left side, 400x500px starting at (100, 300) + # Region 2 (street vendor): right side, 350x500px starting at (560, 300) + values = [ + [100, 300, 400, 500, 1.0], # Region 1 + [560, 300, 350, 500, 1.0] # Region 2 + ] - # Get region data from UI + # Get region data from UI (overrides defaults if workflow saved) try: if extra_pnginfo and "workflow" in extra_pnginfo and "nodes" in extra_pnginfo["workflow"]: for node in extra_pnginfo["workflow"]["nodes"]: if node["id"] == int(unique_id): - values = node["properties"].get("values", []) + saved_values = node["properties"].get("values", []) + if saved_values: # Only override if we actually have saved values + values = saved_values # Override with property dimensions if available prop_width = node["properties"].get("width", width) prop_height = node["properties"].get("height", height) break except Exception as e: - print(f"ℹ️ Using input dimensions {width}x{height}") + print(f"ℹ️ Using default template boxes and dimensions {width}x{height}") prop_width = width prop_height = height @@ -291,12 +314,43 @@ Compatible: Flux (all variants), Chroma, SD3, SD3.5, and other mask-based models print(f"⚠️ Warning: {num_regions} regions detected. Flux works best with 3-4 regions maximum.") # Encode each prompt using CLIP + # Flux uses dual-encoder (CLIP-L + T5-XXL), Chroma/Qwen use single encoder encoded_conditionings = [] + + # Detect if this is Flux by checking for encode_from_tokens_scheduled method + is_flux = hasattr(clip, 'encode_from_tokens_scheduled') + for prompt in prompts: if prompt and prompt.strip(): tokens = clip.tokenize(prompt) - cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True) - encoded_conditionings.append([[cond, {"pooled_output": pooled}]]) + + if is_flux: + # Flux-specific: Dual encoder (CLIP-L + T5-XXL) with guidance + # Check if t5xxl tokens are available + if "t5xxl" in tokens: + # Already has both encoders in single tokenize call + pass + else: + # Need to merge t5xxl tokens manually + try: + t5_result = clip.tokenize(prompt) + if "t5xxl" in t5_result: + tokens["t5xxl"] = t5_result["t5xxl"] + except: + pass + + # Use Flux's scheduled encoding with guidance + try: + cond = clip.encode_from_tokens_scheduled(tokens, add_dict={"guidance": guidance}) + encoded_conditionings.append(cond) + except Exception as e: + print(f"⚠️ Flux encoding failed: {e}, using fallback") + cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True) + encoded_conditionings.append([[cond, {"pooled_output": pooled}]]) + else: + # Standard encoding for Chroma, Qwen, SD3, etc. (single encoder) + cond, pooled = clip.encode_from_tokens(tokens, return_pooled=True) + encoded_conditionings.append([[cond, {"pooled_output": pooled}]]) else: encoded_conditionings.append(None) diff --git a/js/RegionalPrompting.js b/js/RegionalPrompting.js index 6dd35cc..7f12e81 100644 --- a/js/RegionalPrompting.js +++ b/js/RegionalPrompting.js @@ -28,10 +28,10 @@ function addRegionalPrompterCanvas(node, app) { const margin = 10 const border = 2 - const widgetHeight = node.canvasHeight - const values = node.properties["values"] - const width = Math.round(node.properties["width"]) - const height = Math.round(node.properties["height"]) + const widgetHeight = node.canvasHeight || 200 + const values = node.properties["values"] || [] + const width = Math.round(node.properties["width"] || 512) + const height = Math.round(node.properties["height"] || 512) const scale = Math.min((widgetWidth-margin*2)/width, (widgetHeight-margin*2)/height) @@ -45,11 +45,19 @@ function addRegionalPrompterCanvas(node, app) { } const index = indexWidget ? Math.round(indexWidget.value) : 0; + // Ensure canvas element has proper internal dimensions + const canvasDisplayWidth = Math.max(200, widgetWidth * t.a); + const canvasDisplayHeight = Math.max(200, widgetHeight * t.d); + + // Set canvas internal resolution (for drawing) + this.canvas.width = widgetWidth; + this.canvas.height = widgetHeight; + Object.assign(this.canvas.style, { left: `${t.e}px`, top: `${t.f + (widgetY*t.d)}px`, - width: `${widgetWidth * t.a}px`, - height: `${widgetHeight * t.d}px`, + width: `${canvasDisplayWidth}px`, + height: `${canvasDisplayHeight}px`, position: "absolute", zIndex: 1, fontSize: `${t.d * 10.0}px`, @@ -128,21 +136,29 @@ function addRegionalPrompterCanvas(node, app) { ctx.stroke(); ctx.closePath(); - // Draw currently selected zone - let [x, y, w, h] = getDrawArea(values[index]) + // Draw currently selected zone (if values exist) + if (values.length > 0 && index < values.length) { + let [x, y, w, h] = getDrawArea(values[index]) - w = Math.max(32*scale, w) - h = Math.max(32*scale, h) + w = Math.max(32*scale, w) + h = Math.max(32*scale, h) - ctx.fillStyle = "#ffffff" - ctx.fillRect(widgetX+x, widgetY+y, w, h) + ctx.fillStyle = "#ffffff" + ctx.fillRect(widgetX+x, widgetY+y, w, h) - const selectedColor = getDrawColor(index/values.length, "FF") - ctx.fillStyle = selectedColor - ctx.fillRect(widgetX+x+border, widgetY+y+border, w-border*2, h-border*2) + const selectedColor = getDrawColor(index/values.length, "FF") + ctx.fillStyle = selectedColor + ctx.fillRect(widgetX+x+border, widgetY+y+border, w-border*2, h-border*2) - ctx.lineWidth = 1; - ctx.closePath(); + ctx.lineWidth = 1; + ctx.closePath(); + } else { + // No regions defined yet - show helpful message + ctx.fillStyle = "#ffffff80"; + ctx.font = "14px Arial"; + ctx.textAlign = "center"; + ctx.fillText("Add regions by adjusting x/y/width/height below", widgetX + backgroundWidth/2, widgetY + backgroundHeight/2); + } }, }; @@ -150,6 +166,10 @@ function addRegionalPrompterCanvas(node, app) { widget.canvas = document.createElement("canvas"); widget.canvas.className = "dave-custom-canvas"; + // Set initial canvas dimensions (will be updated by draw function) + widget.canvas.width = 512; + widget.canvas.height = 512; + widget.parent = node; document.body.appendChild(widget.canvas); @@ -172,6 +192,11 @@ function addRegionalPrompterCanvas(node, app) { computeCanvasSize(node, size); } + // Force initial canvas size computation + setTimeout(() => { + computeCanvasSize(node, node.size); + }, 100); + return { minWidth: 200, minHeight: 200, widget } }