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
This commit is contained in:
Claude
2025-11-22 02:40:47 +00:00
parent 3a9eeca009
commit 747c881a91
2 changed files with 110 additions and 31 deletions
+68 -14
View File
@@ -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)
+42 -17
View File
@@ -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 }
}