feat: store temp image reference instead of base64 in workflow
File picker previously embedded base64 image data in the image_data widget, bloating workflow JSON past the 5MiB localStorage limit (Failed to save workflow draft). Now uploads via POST /upload/image with type=temp and stores a small '<path> [temp]' reference; backend resolves it with get_annotated_filepath. Base64 data URLs from older saves still load via backward-compat path.
This commit is contained in:
@@ -125,13 +125,13 @@ seed: 42
|
||||
### Architecture
|
||||
- **Python Backend**: Image loading and processing using PIL/Pillow
|
||||
- **JavaScript Frontend**: File picker with FileReader API for browser-based file access
|
||||
- **Base64 Transfer**: Secure data transfer from browser to ComfyUI backend
|
||||
- **Temp Upload**: Image uploaded to ComfyUI temp storage; widget stores a small reference string
|
||||
|
||||
### Browser Limitations
|
||||
Due to browser security restrictions, the file picker:
|
||||
- Cannot access full file system paths
|
||||
- Uses FileReader API to read files directly in browser
|
||||
- Transfers image data as base64-encoded strings
|
||||
- Uploads the image to ComfyUI temp storage and stores a small reference string (avoids workflow JSON bloat)
|
||||
- Works completely client-side until execution
|
||||
|
||||
### Output Format
|
||||
|
||||
+39
-34
@@ -62,7 +62,7 @@ class RandomImagePicker(SaveImage):
|
||||
"image_data": ("STRING", {
|
||||
"default": "",
|
||||
"multiline": True,
|
||||
"tooltip": "Base64 image data from file picker (auto-filled)"
|
||||
"tooltip": "Image reference (temp) from file picker (auto-filled)"
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -179,40 +179,45 @@ class RandomImagePicker(SaveImage):
|
||||
"Please use the 📁 Choose File button to select an image."
|
||||
)
|
||||
|
||||
import base64
|
||||
import io
|
||||
|
||||
try:
|
||||
# Remove data URL prefix if present
|
||||
if ',' in image_data:
|
||||
if image_data.startswith("data:"):
|
||||
# Backward compatibility: base64 data URL from older versions
|
||||
import base64
|
||||
import io
|
||||
|
||||
try:
|
||||
# Remove data URL prefix if present
|
||||
image_data = image_data.split(',', 1)[1]
|
||||
|
||||
# Decode base64
|
||||
img_bytes = base64.b64decode(image_data)
|
||||
img = Image.open(io.BytesIO(img_bytes))
|
||||
|
||||
# Convert to RGB if needed
|
||||
if img.mode != 'RGB':
|
||||
img = img.convert('RGB')
|
||||
|
||||
# Get dimensions
|
||||
width, height = img.size
|
||||
|
||||
# Convert to numpy array
|
||||
img_array = np.array(img).astype(np.float32) / 255.0
|
||||
|
||||
# Convert to torch tensor and add batch dimension
|
||||
img_tensor = torch.from_numpy(img_array)[None,]
|
||||
|
||||
print(f"[Random Image Picker] Mode: Single (File Picker)")
|
||||
print(f"[Random Image Picker] Resolution: {width}x{height}")
|
||||
|
||||
# Save for preview and return with image tensor
|
||||
preview = self.save_images(img_tensor, filename_prefix="RandomPicker")
|
||||
return {"ui": preview["ui"], "result": (img_tensor,)}
|
||||
|
||||
except Exception as e:
|
||||
raise ValueError(f"Failed to load image from file picker: {str(e)}")
|
||||
|
||||
# Decode base64
|
||||
img_bytes = base64.b64decode(image_data)
|
||||
img = Image.open(io.BytesIO(img_bytes))
|
||||
|
||||
# Convert to RGB if needed
|
||||
if img.mode != 'RGB':
|
||||
img = img.convert('RGB')
|
||||
|
||||
# Get dimensions
|
||||
width, height = img.size
|
||||
|
||||
# Convert to numpy array
|
||||
img_array = np.array(img).astype(np.float32) / 255.0
|
||||
|
||||
# Convert to torch tensor and add batch dimension
|
||||
img_tensor = torch.from_numpy(img_array)[None,]
|
||||
|
||||
except Exception as e:
|
||||
raise ValueError(f"Failed to load image from file picker: {str(e)}")
|
||||
else:
|
||||
# New path: resolve temp image reference (e.g. "name.png [temp]")
|
||||
image_path = folder_paths.get_annotated_filepath(image_data)
|
||||
img_tensor, width, height = self.load_image_file(image_path)
|
||||
|
||||
print(f"[Random Image Picker] Mode: Single (File Picker)")
|
||||
print(f"[Random Image Picker] Resolution: {width}x{height}")
|
||||
|
||||
# Save for preview and return with image tensor
|
||||
preview = self.save_images(img_tensor, filename_prefix="RandomPicker")
|
||||
return {"ui": preview["ui"], "result": (img_tensor,)}
|
||||
|
||||
|
||||
# Node registration
|
||||
|
||||
+27
-14
@@ -28,7 +28,7 @@ app.registerExtension({
|
||||
input.type = "file";
|
||||
input.accept = "image/png,image/jpeg,image/jpg,image/webp,image/bmp,image/gif";
|
||||
|
||||
input.onchange = (e) => {
|
||||
input.onchange = async (e) => {
|
||||
if (e.target.files && e.target.files[0]) {
|
||||
const file = e.target.files[0];
|
||||
|
||||
@@ -38,28 +38,41 @@ app.registerExtension({
|
||||
folderModeWidget.value = false;
|
||||
}
|
||||
|
||||
// Read file as base64
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
const base64Data = event.target.result;
|
||||
try {
|
||||
// Upload image to ComfyUI temp storage
|
||||
const body = new FormData();
|
||||
body.append('image', file);
|
||||
body.append('type', 'temp');
|
||||
|
||||
// Store image_data in widget
|
||||
let imageDataWidget = node.widgets.find(w => w.name === "image_data");
|
||||
if (imageDataWidget) {
|
||||
imageDataWidget.value = base64Data;
|
||||
const api = app.api;
|
||||
const fetchFn = api.apiFetch ? api.apiFetch.bind(api) : api.fetchApi.bind(api);
|
||||
const resp = await fetchFn('/upload/image', { method: 'POST', body });
|
||||
if (resp.status !== 200) {
|
||||
throw new Error(`Upload failed with status ${resp.status}`);
|
||||
}
|
||||
|
||||
// Show instant preview
|
||||
const data = await resp.json();
|
||||
|
||||
// Store reference string in widget (not base64)
|
||||
const path = data.subfolder ? `${data.subfolder}/${data.name}` : data.name;
|
||||
const ref = `${path} [temp]`;
|
||||
|
||||
let imageDataWidget = node.widgets.find(w => w.name === "image_data");
|
||||
if (imageDataWidget) {
|
||||
imageDataWidget.value = ref;
|
||||
}
|
||||
|
||||
// Show instant preview from temp storage
|
||||
const img = new Image();
|
||||
img.onload = () => {
|
||||
node.imgs = [img];
|
||||
node.setSizeForImage?.();
|
||||
app.graph.setDirtyCanvas(true);
|
||||
};
|
||||
img.src = base64Data;
|
||||
};
|
||||
|
||||
reader.readAsDataURL(file);
|
||||
img.src = api.apiURL('/view?filename=' + encodeURIComponent(data.name) + '&type=temp&subfolder=' + encodeURIComponent(data.subfolder || ''));
|
||||
} catch (err) {
|
||||
console.error("[Random Image Picker] Upload failed:", err);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user