From bd275c4941a89f448f19a04d7fda3087ae49deb3 Mon Sep 17 00:00:00 2001 From: solidlime Date: Wed, 7 Jan 2026 21:21:19 +0900 Subject: [PATCH] Major redesign: Direct file reading for single mode Breaking changes: - Single mode: Now uses file picker with direct browser-based image reading - Folder mode: Uses manual path input only - Renamed 'image' parameter to 'folder_path' for clarity - Added 'image_data' optional parameter for base64 image data Implementation: - JavaScript: FileReader API to read image as base64 - Python: Decode base64 and convert to tensor (no file system access needed) - Better UX: No confusing error messages about missing paths - Clear separation: File picker for single, text input for folder Benefits: - Single mode works without file system paths - No browser security issues - Cleaner user experience - Explicit mode separation --- random_image_picker.py | 75 +++++++++++++++++++++++++++----------- web/random_image_picker.js | 46 +++++++++++++---------- 2 files changed, 80 insertions(+), 41 deletions(-) diff --git a/random_image_picker.py b/random_image_picker.py index 80c3c80..9686657 100644 --- a/random_image_picker.py +++ b/random_image_picker.py @@ -28,17 +28,17 @@ class RandomImagePicker: return { "required": { - "image": ("STRING", { + "folder_path": ("STRING", { "default": default_path, "multiline": False, "dynamicPrompts": False, - "tooltip": "Enter full path to image file (Single mode) or folder (Folder mode). Use 📁 button to help select." + "tooltip": "Folder path for random image selection (Folder mode only)" }), "folder_mode": ("BOOLEAN", { "default": False, "label_on": "Folder", "label_off": "Single", - "tooltip": "Single: Load specific file | Folder: Random from folder" + "tooltip": "Single: Use file picker | Folder: Random from folder path" }), "include_subfolders": ("BOOLEAN", { "default": False, @@ -53,6 +53,13 @@ class RandomImagePicker: "tooltip": "Random seed for reproducible selection in Folder mode" }), }, + "optional": { + "image_data": ("STRING", { + "default": "", + "multiline": True, + "forceInput": True, + }), + } } RETURN_TYPES = ("IMAGE", "INT", "INT") @@ -119,32 +126,27 @@ class RandomImagePicker: return img_tensor, width, height - def load_image(self, image: str, folder_mode: bool, include_subfolders: bool, seed: int): + def load_image(self, folder_path: str, folder_mode: bool, include_subfolders: bool, seed: int, image_data: str = ""): """ Load image based on mode settings. Args: - image: File path (single mode) or file path to extract folder from (folder mode) + folder_path: Folder path for random selection (folder mode) folder_mode: True for folder mode, False for single file mode include_subfolders: Whether to scan subfolders seed: Random seed for folder mode + image_data: Base64 encoded image data from file picker (single mode) Returns: Tuple of (image_tensor, width, height) """ - if not image or not os.path.exists(image): - raise ValueError(f"Path does not exist: {image}") - if folder_mode: - # Folder mode: extract folder from file path and pick random image - if os.path.isfile(image): - # Extract folder path from file path - folder_path = os.path.dirname(image) - elif os.path.isdir(image): - # Direct folder path - folder_path = image - else: - raise ValueError(f"Invalid path: {image}") + # Folder mode: pick random image from folder + if not folder_path or not os.path.exists(folder_path): + raise ValueError(f"Folder path does not exist: {folder_path}") + + if not os.path.isdir(folder_path): + raise ValueError(f"Folder mode requires a directory path: {folder_path}") image_files = self.get_image_files(folder_path, include_subfolders) @@ -159,12 +161,41 @@ class RandomImagePicker: print(f"[Random Image Picker] Selected: {selected_image}") return self.load_image_file(selected_image) else: - # Single file mode - if not os.path.isfile(image): - raise ValueError(f"Single file mode enabled but path is not a file: {image}") + # Single file mode: use image_data from file picker + if not image_data: + raise ValueError("Please select an image file using the 📁 Choose File button") - print(f"[Random Image Picker] Loading: {image}") - return self.load_image_file(image) + # Parse base64 image data + import base64 + import io + + try: + # Remove data URL prefix if present + if ',' in image_data: + 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] Loaded from file picker: {width}x{height}") + return (img_tensor, width, height) + + except Exception as e: + raise ValueError(f"Failed to load image from file picker: {str(e)}") # Node registration diff --git a/web/random_image_picker.js b/web/random_image_picker.js index a8c80cb..7828c6e 100644 --- a/web/random_image_picker.js +++ b/web/random_image_picker.js @@ -11,8 +11,16 @@ app.registerExtension({ const result = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined; const node = this; - // Add browse button + // Add file picker button node.addWidget("button", "📁 Choose File", null, () => { + const folderModeWidget = node.widgets.find(w => w.name === "folder_mode"); + const isFolderMode = folderModeWidget?.value || false; + + if (isFolderMode) { + alert("⚠️ Folder Mode is enabled.\n\nPlease switch to Single mode to use the file picker,\nor enter the folder path in the 'folder_path' field above."); + return; + } + const input = document.createElement("input"); input.type = "file"; input.accept = "image/png,image/jpeg,image/jpg,image/webp,image/bmp,image/gif"; @@ -20,27 +28,27 @@ app.registerExtension({ input.onchange = (e) => { if (e.target.files && e.target.files[0]) { const file = e.target.files[0]; - const imageWidget = node.widgets.find(w => w.name === "image"); - if (imageWidget) { - // Show selected file name - const fileName = file.name; - console.log("Selected file:", fileName); + // Read file as base64 + const reader = new FileReader(); + reader.onload = (event) => { + const base64Data = event.target.result; - // Show dialog with instructions - const currentPath = imageWidget.value || ""; - const dialogText = `File selected: ${fileName}\n\n` + - `⚠️ IMPORTANT:\n` + - `Due to browser security, the full path cannot be automatically detected.\n\n` + - `Please copy and paste the FULL PATH to this file in the 'image' field above.\n\n` + - `Example:\n` + - `D:\\Images\\${fileName}\n` + - `or\n` + - `C:\\Users\\YourName\\Pictures\\${fileName}\n\n` + - `Current default: ${currentPath || '(not set)'}`; + // Find or create the image_data widget + let imageDataWidget = node.widgets.find(w => w.name === "image_data"); + if (imageDataWidget) { + imageDataWidget.value = base64Data; + } - alert(dialogText); - } + console.log(`[Random Image Picker] File loaded: ${file.name} (${(base64Data.length / 1024).toFixed(2)} KB)`); + alert(`✅ Image loaded successfully!\n\nFile: ${file.name}\nSize: ${(file.size / 1024).toFixed(2)} KB\n\nYou can now run the workflow.`); + }; + + reader.onerror = () => { + alert("❌ Failed to read the image file.\n\nPlease try again."); + }; + + reader.readAsDataURL(file); } };