This commit is contained in:
justumen
2024-09-13 16:59:55 +02:00
parent e0abed117e
commit 4965e2fc72
9 changed files with 194 additions and 2 deletions

View File

@@ -68,6 +68,7 @@ wget --content-disposition -P /workspace/ComfyUI/models/checkpoints "https://civ
- **v0.22**: Allow write text node to use random selection like this {hood|helmet} will randomly choose between hood or helmet.
- **v0.23**: Add a new node: Pause, resume or stop workflow.
- **v0.24**: Add a new node: Pause, select input, pick one.
- **v0.25**: Two new nodes: Loop Images and Random image.
# 📝 Nodes descriptions
@@ -348,4 +349,20 @@ You can connect any type of node to the pause node, above is an example with tex
**Description:**
Automatically pause the workflow, and rings a bell when it does. (play the audio `bell.m4a` file provided)
You can then manually select the input you want to use, and resume the workflow with it.
You can connect this node to anything you want, above is an example with IMAGE. But you can pick whatever you want, in the node `input = output`.
You can connect this node to anything you want, above is an example with IMAGE. But you can pick whatever you want, in the node `input = output`.
### 37 - 🎲🖼 Random Image
![pick input](screenshots/random_image.png)
**Description:**
Just take a random image from a list of images.
### 38 - ♻🖼 Loop (Images)
![pick input](screenshots/loop_images.png)
**Description:**
Loop over a list of images.
Usage example : You have a list of images, and you want to apply the same process to all of them.
Above is an example of the loop images node sending them to an Ipadapter style transfer workflow. (Same seed of course.)

View File

@@ -42,6 +42,9 @@ from .loop_combine_texts_by_lines import CombineTextsByLines
from .free_vram_hack import FreeVRAM
from .pause_resume_stop import PauseResume
from .pick_input import PickInput
from .loop_images import LoopImages
from .random_image import RandomImage
# from .pass_preview_image import PassPreviewImage
# from .check_black_image import CheckBlackImage
# from .clear_vram import ClearVRAM
@@ -51,6 +54,8 @@ from .pick_input import PickInput
NODE_CLASS_MAPPINGS = {
# "Bjornulf_CustomStringType": CustomStringType,
"Bjornulf_ollamaLoader": ollamaLoader,
"Bjornulf_LoopImages": LoopImages,
"Bjornulf_RandomImage": RandomImage,
# "Bjornulf_PassPreviewImage": PassPreviewImage,
"Bjornulf_PickInput": PickInput,
"Bjornulf_PauseResume": PauseResume,
@@ -102,6 +107,8 @@ NODE_CLASS_MAPPINGS = {
NODE_DISPLAY_NAME_MAPPINGS = {
# "Bjornulf_CustomStringType": "!!! CUSTOM STRING TYPE !!!",
"Bjornulf_ollamaLoader": "🦙 Ollama (Description)",
"Bjornulf_LoopImages": "♻🖼 Loop (Images)",
"Bjornulf_RandomImage": "🎲🖼 Random Image",
# "Bjornulf_PassPreviewImage": "🖼⮕ Pass Preview Image",
"Bjornulf_PickInput": "⏸️🔍 Paused. Select input, Pick one",
"Bjornulf_PauseResume": "⏸️ Paused. Resume or Stop ?",

26
loop_images.py Normal file
View File

@@ -0,0 +1,26 @@
class LoopImages:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"number_of_images": ("INT", {"default": 2, "min": 1, "max": 10, "step": 1}),
},
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "loop_images"
OUTPUT_IS_LIST = (True,)
CATEGORY = "Bjornulf"
def loop_images(self, number_of_images, **kwargs):
image_list = []
for i in range(1, number_of_images + 1):
image_key = f"image_{i}"
if image_key in kwargs and kwargs[image_key] is not None:
image_list.append(kwargs[image_key])
return (image_list,)
@classmethod
def IS_CHANGED(cls, number_of_images, ** kwargs):
return float("nan") # This will force the node to always update

View File

@@ -1,7 +1,7 @@
[project]
name = "bjornulf_custom_nodes"
description = "Nodes: Ollama, Text to Speech, Combine Texts, Random Texts, Save image for Bjornulf LobeChat, Text with random Seed, Random line from input, Combine images, Image to grayscale (black & white), Remove image Transparency (alpha), Resize Image, ..."
version = "0.24"
version = "0.25"
license = {file = "LICENSE"}
[project.urls]

32
random_image.py Normal file
View File

@@ -0,0 +1,32 @@
import random
class RandomImage:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"number_of_images": ("INT", {"default": 2, "min": 1, "max": 10, "step": 1}),
},
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "random_image"
CATEGORY = "Bjornulf"
def random_image(self, number_of_images, **kwargs):
valid_images = []
for i in range(1, number_of_images + 1):
image_key = f"image_{i}"
if image_key in kwargs and kwargs[image_key] is not None:
valid_images.append(kwargs[image_key])
if not valid_images:
raise ValueError("No valid images provided")
random_image = random.choice(valid_images)
return (random_image,)
@classmethod
def IS_CHANGED(cls, number_of_images, ** kwargs):
return float("nan") # This will force the node to always update

BIN
screenshots/loop_images.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 940 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 224 KiB

52
web/js/loop_images.js Normal file
View File

@@ -0,0 +1,52 @@
import { app } from "../../../scripts/app.js";
app.registerExtension({
name: "Bjornulf.LoopImages",
async nodeCreated(node) {
if (node.comfyClass === "Bjornulf_LoopImages") {
const updateInputs = () => {
const numInputsWidget = node.widgets.find(w => w.name === "number_of_images");
if (!numInputsWidget) return;
const numInputs = numInputsWidget.value;
// Initialize node.inputs if it doesn't exist
if (!node.inputs) {
node.inputs = [];
}
// Filter existing image inputs
const existingInputs = node.inputs.filter(input => input.name.startsWith('image_'));
// Determine if we need to add or remove inputs
if (existingInputs.length < numInputs) {
// Add new image inputs if not enough existing
for (let i = existingInputs.length + 1; i <= numInputs; i++) {
const inputName = `image_${i}`;
if (!node.inputs.find(input => input.name === inputName)) {
node.addInput(inputName, "IMAGE");
}
}
} else {
// Remove excess image inputs if too many
node.inputs = node.inputs.filter(input => !input.name.startsWith('image_') || parseInt(input.name.split('_')[1]) <= numInputs);
}
node.setSize(node.computeSize());
};
// Move number_of_images to the top initially
const numInputsWidget = node.widgets.find(w => w.name === "number_of_images");
if (numInputsWidget) {
node.widgets = [numInputsWidget, ...node.widgets.filter(w => w !== numInputsWidget)];
numInputsWidget.callback = () => {
updateInputs();
app.graph.setDirtyCanvas(true);
};
}
// Delay the initial update to ensure node is fully initialized
setTimeout(updateInputs, 0);
}
}
});

58
web/js/random_image.js Normal file
View File

@@ -0,0 +1,58 @@
import { app } from "../../../scripts/app.js";
app.registerExtension({
name: "Bjornulf.RandomImage",
async nodeCreated(node) {
if (node.comfyClass === "Bjornulf_RandomImage") {
const updateInputs = () => {
const numInputsWidget = node.widgets.find(w => w.name === "number_of_images");
if (!numInputsWidget) return;
const numInputs = numInputsWidget.value;
// Initialize node.inputs if it doesn't exist
if (!node.inputs) {
node.inputs = [];
}
// Filter existing image inputs
const existingInputs = node.inputs.filter(input => input.name.startsWith('image_'));
// Determine if we need to add or remove inputs
if (existingInputs.length < numInputs) {
// Add new image inputs if not enough existing
for (let i = existingInputs.length + 1; i <= numInputs; i++) {
const inputName = `image_${i}`;
if (!node.inputs.find(input => input.name === inputName)) {
node.addInput(inputName, "IMAGE");
}
}
} else {
// Remove excess image inputs if too many
node.inputs = node.inputs.filter(input => !input.name.startsWith('image_') || parseInt(input.name.split('_')[1]) <= numInputs);
}
node.setSize(node.computeSize());
};
// Set seed widget to hidden input
const seedWidget = node.widgets.find(w => w.name === "seed");
if (seedWidget) {
seedWidget.type = "HIDDEN";
}
// Move number_of_images to the top initially
const numInputsWidget = node.widgets.find(w => w.name === "number_of_images");
if (numInputsWidget) {
node.widgets = [numInputsWidget, ...node.widgets.filter(w => w !== numInputsWidget)];
numInputsWidget.callback = () => {
updateInputs();
app.graph.setDirtyCanvas(true);
};
}
// Delay the initial update to ensure node is fully initialized
setTimeout(updateInputs, 0);
}
}
});