Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0befe164cc | ||
|
|
d506c68a80 | ||
|
|
c59c429b75 | ||
|
|
c726b6e4a2 | ||
|
|
96075ad4e1 | ||
|
|
7a8dc07a8a | ||
|
|
1fdac0bc09 | ||
|
|
804b942a36 | ||
|
|
6335d4378b | ||
|
|
cfc2189616 |
@@ -57,9 +57,16 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
|
||||
|
||||
[workflow-5](./workflow/5-gpt-workflow.json)
|
||||
|
||||
### 3D
|
||||

|
||||
[workflow](./workflow/3D-workflow.json)
|
||||
|
||||
## Prompt
|
||||
> PromptSlide
|
||||

|
||||
|
||||

|
||||
|
||||
> randomPrompt
|
||||
|
||||

|
||||
|
||||
|
||||
### Layers
|
||||
@@ -70,6 +77,11 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
|
||||

|
||||
|
||||
|
||||
### 3D
|
||||

|
||||
[workflow](./workflow/3D-workflow.json)
|
||||
|
||||
|
||||
### LoadImagesFromLocal
|
||||
> Monitor changes to images in a local folder, and trigger real-time execution of workflows, supporting common image formats, especially PSD format, in conjunction with Photoshop.
|
||||
|
||||
@@ -88,6 +100,7 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
|
||||
|
||||
- [Added DynamicDelayByText, enabling delayed execution based on input text length.](./workflow/audio-chatgpt-workflow.json)
|
||||
|
||||
|
||||
## Other Nodes
|
||||
|
||||

|
||||
@@ -95,9 +108,7 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
|
||||
|
||||
[workflow-1](./workflow/1-workflow.json)
|
||||
|
||||
> randomPrompt
|
||||
|
||||

|
||||
|
||||
> TransparentImage
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 477 KiB |
@@ -0,0 +1,10 @@
|
||||
Chibi Anime Style
|
||||
Gakuen Anime Style
|
||||
Gekiga Anime Style
|
||||
Jidaimono Anime Style
|
||||
Kawaii Anime Style
|
||||
Mecha Anime Style
|
||||
Realistic Anime Style
|
||||
Semi-Realistic Anime Style
|
||||
Shoji Anime Style
|
||||
Kemonomimi Anime Style
|
||||
+2052
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,23 @@
|
||||
GoPro
|
||||
Drone
|
||||
polaroid
|
||||
black and white film
|
||||
Kodachrome
|
||||
shot on 8mm
|
||||
shot on 16mm
|
||||
shot on 35mm
|
||||
Microscopic
|
||||
Fisheye Lens
|
||||
Wide Angle
|
||||
Ultra-Wide Angle
|
||||
Panorama
|
||||
Short Exposure
|
||||
Long Exposure
|
||||
Double Exposure
|
||||
f2.8
|
||||
Depth of Field
|
||||
Soft Focus
|
||||
Deep Focus
|
||||
Shallow Focus
|
||||
Vanishing Point
|
||||
Vantage Point
|
||||
@@ -0,0 +1,16 @@
|
||||
Mood Lighting
|
||||
Moody Lighting
|
||||
Studio Lighting
|
||||
Cove Lighting
|
||||
Soft Lighting
|
||||
Hard Lighting
|
||||
Volumetric Lighting
|
||||
Low-Key Lighting
|
||||
High-Key Lighting
|
||||
Epic Light
|
||||
Rembrandt Lighting
|
||||
Contre-Jour
|
||||
Veiling Flare
|
||||
Crepuscular Rays
|
||||
Rays of Shimmering Light
|
||||
Godrays
|
||||
@@ -0,0 +1,132 @@
|
||||
Aaron Siskind
|
||||
Alessio Albi
|
||||
Alfred Eisenstaedt
|
||||
Alfred Stieglitz
|
||||
Alyssa Monks
|
||||
André Kertész
|
||||
Andreas Gursky
|
||||
Andrew Wyeth
|
||||
Anne Geddes
|
||||
Annie Leibovitz
|
||||
Ansel Adams
|
||||
Arnold Newman
|
||||
August Sander
|
||||
Balthus
|
||||
Berenice Abbott
|
||||
Bill Brandt
|
||||
Bill Henson
|
||||
Brassaï (Gyula Halász)
|
||||
Brooke Shaden
|
||||
Bruce Davidson
|
||||
Bruce Weber
|
||||
Bunny Yeager
|
||||
Carleton Watkins
|
||||
Carrie Mae Weems
|
||||
Chuck Close
|
||||
Cindy Sherman
|
||||
Clarence H. White
|
||||
Claude Cahun
|
||||
Danny Lyon
|
||||
David LaChapelle
|
||||
Dawoud Bey
|
||||
Diane Arbus
|
||||
Don McCullin
|
||||
Dora Maar
|
||||
Dorothea Lange
|
||||
Duane Michals
|
||||
Eadweard Muybridge
|
||||
Edward Burtynsky
|
||||
Edward Curtis
|
||||
Edward Ruscha
|
||||
Edward Steichen
|
||||
Edward Weston
|
||||
Elliott Erwitt
|
||||
Ernst Haas
|
||||
Eugene Atget
|
||||
Fan Ho
|
||||
Francesca Woodman
|
||||
Frans Lanting
|
||||
Garry Winogrand
|
||||
Georges Melies
|
||||
Gerda Taro
|
||||
Gertrude Käsebier
|
||||
Gordon Parks
|
||||
Graciela Iturbide
|
||||
Gregory Crewdson
|
||||
Harold Edgerton
|
||||
Helen Levitt
|
||||
Helmut Newton
|
||||
Hendrik Kerstens
|
||||
Henri Cartier-Bresson
|
||||
Hugh Kretschmer
|
||||
Irving Penn
|
||||
Jacques Henri Lartigue
|
||||
James Nachtwey
|
||||
James Van Der Zee
|
||||
Jay Maisel
|
||||
Jerry Uelsmann
|
||||
Joel Peter Witkin
|
||||
Joel Sartore
|
||||
John Frederick William Herschel
|
||||
Josef Sudek
|
||||
Julia Margaret Cameron
|
||||
Karl Blossfeldt
|
||||
Larry Burrows
|
||||
László Moholy-Nagy (photography)
|
||||
Lee Jeffries
|
||||
Lewis Hine
|
||||
Lorna Simpson
|
||||
Lynsey Addario
|
||||
Margaret Bourke-White
|
||||
Mario Testino
|
||||
Martin Parr
|
||||
Martin Schoeller
|
||||
Mary Ellen Mark
|
||||
Mathew B. Brady
|
||||
Méret Oppenheim
|
||||
Meryl McMaster
|
||||
Mick Rock
|
||||
Miles Aldridge
|
||||
Minor Martin White
|
||||
Nan Goldin
|
||||
Nathan Wirth
|
||||
Olive Cotton
|
||||
Olivier Rousteing
|
||||
Patrick Demarchelier
|
||||
Paul Nicklen
|
||||
Paul Outerbridge
|
||||
Paul Strand
|
||||
Pete Souza
|
||||
Peter Dombrovskis
|
||||
Peter Henry Emerson
|
||||
Peter Lik
|
||||
Peter Lindbergh
|
||||
Philip-Lorca diCorcia
|
||||
Philippe Halsman
|
||||
Ralph Gibson
|
||||
Richard Avedon
|
||||
Robert Adams
|
||||
Robert Bechtle
|
||||
Robert Capa
|
||||
Robert Frank
|
||||
Robert Mapplethorpe
|
||||
Roger Fenton
|
||||
Ruth Bernhard
|
||||
Sally Mann
|
||||
Sebastião Salgado
|
||||
Shirin Neshat
|
||||
Stefan Gesell
|
||||
Steven Meisel
|
||||
Susan Meiselas
|
||||
Vivian Maier
|
||||
Vivian Maier
|
||||
Viviane Sassen
|
||||
Walker Evans
|
||||
Wes Anderson
|
||||
William Eggleston
|
||||
William Eugene Smith
|
||||
William Henry Fox Talbot
|
||||
Yinka Shonibare
|
||||
Yousuf Karsh
|
||||
Man Ray
|
||||
Robert Mapplethorpe
|
||||
+135
@@ -0,0 +1,135 @@
|
||||
Vintage
|
||||
Grain
|
||||
Sepia
|
||||
High Key
|
||||
Low Key
|
||||
High Dynamic Range
|
||||
Cross Process
|
||||
Radial Blur
|
||||
Infrared
|
||||
Lomo
|
||||
Photocopy
|
||||
Pencil Sketch
|
||||
Pop Art
|
||||
Orton
|
||||
Mosaic
|
||||
Selective Black and White
|
||||
Torn Paper
|
||||
Tilt-Shift
|
||||
Double Exposure
|
||||
Polaroid
|
||||
Liquid Ink
|
||||
Color Splash
|
||||
Sketch
|
||||
Water Drops
|
||||
Polarizer
|
||||
Chinese Painting
|
||||
Water Droplets
|
||||
Polarization
|
||||
Color Inversion
|
||||
Fish-eye
|
||||
Soft Focus
|
||||
Solarization
|
||||
Posterize
|
||||
Comic Book
|
||||
Duotone
|
||||
Gradient Map
|
||||
Edge Detection
|
||||
Oil Painting
|
||||
Reflection
|
||||
Mirror
|
||||
ASCII Art
|
||||
Glitch
|
||||
Time-Lapse
|
||||
Day to Night
|
||||
Surreal
|
||||
Black and White
|
||||
Sepia Tone
|
||||
Vintage Film
|
||||
Grainy Texture
|
||||
High Key Lighting
|
||||
Low Key Lighting
|
||||
Cross Processed Film
|
||||
Infrared Photography
|
||||
Photocopy
|
||||
Pencil Drawing
|
||||
Pop Art Filter
|
||||
Mosaic Filter
|
||||
Selective Desaturation
|
||||
Torn Paper
|
||||
Tilt-Shift Photography
|
||||
Double Exposure
|
||||
Polaroid Style Frame
|
||||
Water Drops Texture
|
||||
Polarizer
|
||||
Chinese Painting
|
||||
Water Droplets Texture
|
||||
Polarization
|
||||
Color Inversion
|
||||
Fish-eye Lens
|
||||
Soft Focus
|
||||
Solarize Filter
|
||||
Edge Detection
|
||||
Oil Painting
|
||||
Reflection
|
||||
Mirror Image
|
||||
Time-Lapse Photography
|
||||
Day to Night Transition
|
||||
Surreal Art Style
|
||||
Abstract Expressionism
|
||||
Acrylic Painting
|
||||
Anime
|
||||
Art Deco
|
||||
Biomorphic Abstraction
|
||||
Black and White Photograph
|
||||
Cartoon
|
||||
Charcoal Sketch
|
||||
Chibi Anime
|
||||
Chinese Painting
|
||||
Classicist Painting
|
||||
Collage
|
||||
Concept Art
|
||||
Cyberpunk
|
||||
Dada Art
|
||||
Digital Art
|
||||
Fantasy Art
|
||||
Fashion Art
|
||||
Fashion Sketch
|
||||
Fish-Eye lens Photograph
|
||||
Goth Art
|
||||
Graffiti
|
||||
Harlem Renaissance
|
||||
High Key Photograph
|
||||
Hyperrealist Pencil Sketch
|
||||
Impressionist Painting
|
||||
Josei Anime
|
||||
Long Exposure Photograph
|
||||
Low Key Photograph
|
||||
Macro Photograph
|
||||
Manga
|
||||
Metal Sculpture
|
||||
Mid Century Modern Illustration
|
||||
Mixed Media
|
||||
Modern Art
|
||||
Moe Anime
|
||||
Nihonga
|
||||
Origami
|
||||
Paper Mache
|
||||
Pen and Ink
|
||||
Pencil Sketch
|
||||
Photograph
|
||||
Photorealism
|
||||
Pinup Art
|
||||
Romanticist Painting
|
||||
Sci-Fi Art
|
||||
Semi Realistic Fantasy Art
|
||||
Semi Realistic Cyberpunk Art
|
||||
Shallow Depth of Field Photograph
|
||||
Steam Punk Art
|
||||
Stone Sculpture
|
||||
Superhero Comic
|
||||
Surrealist Art
|
||||
Tempura Painting
|
||||
Underground Comic
|
||||
Watercolor Painting
|
||||
Zulu Urban Art
|
||||
+84
-16
@@ -254,10 +254,59 @@ def get_images_filepath(f,white_bg=False):
|
||||
|
||||
return images
|
||||
|
||||
|
||||
|
||||
def get_average_color_image(image):
|
||||
# 打开图片
|
||||
# image = Image.open(image_path)
|
||||
|
||||
# 将图片转换为RGB模式
|
||||
image = image.convert("RGB")
|
||||
|
||||
# 获取图片的像素值
|
||||
pixel_data = image.load()
|
||||
|
||||
# 初始化颜色总和和像素数量
|
||||
total_red = 0
|
||||
total_green = 0
|
||||
total_blue = 0
|
||||
pixel_count = 0
|
||||
|
||||
# 遍历图片的每个像素
|
||||
for i in range(image.width):
|
||||
for j in range(image.height):
|
||||
# 获取像素的RGB值
|
||||
r, g, b = pixel_data[i, j]
|
||||
|
||||
# 累加颜色值
|
||||
total_red += r
|
||||
total_green += g
|
||||
total_blue += b
|
||||
|
||||
# 像素数量加1
|
||||
pixel_count += 1
|
||||
|
||||
# 计算平均颜色值
|
||||
average_red = int(total_red // pixel_count)
|
||||
average_green = int(total_green // pixel_count)
|
||||
average_blue = int(total_blue // pixel_count)
|
||||
|
||||
# 返回平均颜色值
|
||||
|
||||
im = Image.new("RGB", (image.width, image.height), (average_red, average_green, average_blue))
|
||||
|
||||
return im
|
||||
|
||||
|
||||
|
||||
# 创建噪声图像
|
||||
def create_noisy_image(width, height, mode="RGB", noise_level=128):
|
||||
def create_noisy_image(width, height, mode="RGB", noise_level=128, background_color="#FFFFFF"):
|
||||
|
||||
background_rgb = tuple(int(background_color[i:i+2], 16) for i in (1, 3, 5))
|
||||
image = Image.new(mode, (width, height), background_rgb)
|
||||
|
||||
# 创建空白图像
|
||||
image = Image.new(mode, (width, height))
|
||||
# image = Image.new(mode, (width, height))
|
||||
|
||||
# 遍历每个像素,并随机设置像素值
|
||||
pixels = image.load()
|
||||
@@ -870,7 +919,7 @@ class LoadImagesFromPath:
|
||||
def INPUT_TYPES(s):
|
||||
return {
|
||||
"required": {
|
||||
"file_path": ("STRING",{"multiline": False,"default": ""}),
|
||||
"file_path": ("STRING",{"multiline": False,"default": "","dynamicPrompts": False}),
|
||||
},
|
||||
"optional":{
|
||||
"white_bg": (["disable","enable"],),
|
||||
@@ -1573,7 +1622,7 @@ class NoiseImage:
|
||||
"step": 1,
|
||||
"display": "slider"
|
||||
}),
|
||||
|
||||
"color_hex": ("STRING",{"multiline": False,"default": "#FFFFFF","dynamicPrompts": False}),
|
||||
},
|
||||
}
|
||||
|
||||
@@ -1592,9 +1641,9 @@ class NoiseImage:
|
||||
# 输出是否为列表
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
def run(self,width,height,noise_level):
|
||||
def run(self,width,height,noise_level,color_hex):
|
||||
# 创建噪声图像
|
||||
im=create_noisy_image(width,height,"RGB",noise_level)
|
||||
im=create_noisy_image(width,height,"RGB",noise_level,color_hex)
|
||||
|
||||
#获取临时目录:temp
|
||||
output_dir = folder_paths.get_temp_directory()
|
||||
@@ -1649,32 +1698,51 @@ class ResizeImage:
|
||||
|
||||
"optional":{
|
||||
"image": ("IMAGE",),
|
||||
"average_color": (["on",'off'],),
|
||||
}
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_TYPES = ("IMAGE","IMAGE")
|
||||
RETURN_NAMES = ("image","average_image",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/image"
|
||||
|
||||
INPUT_IS_LIST = True
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
OUTPUT_IS_LIST = (True,True,)
|
||||
|
||||
def run(self,width,height,scale_option,image=None):
|
||||
def run(self,width,height,scale_option,image=None,average_color=['on']):
|
||||
|
||||
w=width[0]
|
||||
h=height[0]
|
||||
scale_option=scale_option[0]
|
||||
average_color=average_color[0]
|
||||
|
||||
imgs=[]
|
||||
average_images=[]
|
||||
|
||||
if image==None:
|
||||
im=create_noisy_image(w,h,"RGB")
|
||||
else:
|
||||
im=image[0]
|
||||
im=tensor2pil(im)
|
||||
im=resize_image(im,scale_option,w,h)
|
||||
im=im.convert('RGB')
|
||||
a_im=get_average_color_image(im)
|
||||
|
||||
im=pil2tensor(im)
|
||||
imgs.append(im)
|
||||
|
||||
im=pil2tensor(im)
|
||||
a_im=pil2tensor(a_im)
|
||||
average_images.append(a_im)
|
||||
else:
|
||||
for im in image:
|
||||
im=tensor2pil(im)
|
||||
im=resize_image(im,scale_option,w,h)
|
||||
im=im.convert('RGB')
|
||||
|
||||
a_im=get_average_color_image(im)
|
||||
|
||||
im=pil2tensor(im)
|
||||
imgs.append(im)
|
||||
|
||||
a_im=pil2tensor(a_im)
|
||||
average_images.append(a_im)
|
||||
|
||||
return (im,)
|
||||
return (imgs,average_images,)
|
||||
+2
-1
@@ -62,7 +62,8 @@ class PromptSlide:
|
||||
"prompt_keyword": ("STRING",
|
||||
{
|
||||
"multiline": False,
|
||||
"default": ''
|
||||
"default": '',
|
||||
"dynamicPrompts": False
|
||||
}),
|
||||
|
||||
"weight":("FLOAT", {"default": 1, "min": -3,"max": 3,
|
||||
|
||||
+123
-31
@@ -347,21 +347,29 @@
|
||||
}
|
||||
|
||||
|
||||
function randomSeed(data) {
|
||||
for (const key in data) {
|
||||
if (data[key].inputs.seed != undefined) {
|
||||
data[key].inputs.seed = Math.round(Math.random() * 1849378600828930)
|
||||
console.log('new Seed', data[key])
|
||||
function randomSeed(seed, data) {
|
||||
for (const id in data) {
|
||||
|
||||
if (data[id].inputs.seed != undefined
|
||||
&& !Array.isArray(data[id].inputs.seed) //如果是数组,则由其他节点控制
|
||||
&& ['increment', 'decrement', 'randomize'].includes(seed[id])) {
|
||||
data[id].inputs.seed = Math.round(Math.random() * 1849378600828930)
|
||||
console.log('new Seed', data[id])
|
||||
}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
function updateSeed(id, val) {
|
||||
console.log(val)
|
||||
if (!Array.isArray(window._appData.data[id].inputs.seed)) window._appData.data[id].inputs.seed = Math.round(val);
|
||||
}
|
||||
|
||||
function queuePrompt(promptWorkflow, client_id) {
|
||||
|
||||
function queuePrompt(promptWorkflow, seed, client_id) {
|
||||
|
||||
// 随机seed
|
||||
// promptWorkflow = randomSeed(promptWorkflow);
|
||||
promptWorkflow = randomSeed(seed, promptWorkflow);
|
||||
|
||||
let url = get_url()
|
||||
const data = JSON.stringify({ prompt: promptWorkflow, client_id });
|
||||
@@ -436,7 +444,15 @@
|
||||
action.appendChild(copyText)
|
||||
copyText.style.marginLeft = '18px';
|
||||
|
||||
if (link) {
|
||||
let isURL = false;
|
||||
try {
|
||||
new URL(link)
|
||||
isURL = true;
|
||||
} catch (error) {
|
||||
isURL = false;
|
||||
}
|
||||
// new URL(link)
|
||||
if (isURL) {
|
||||
const linkBtn = document.createElement('button');
|
||||
// linkBtn.href = link;
|
||||
linkBtn.innerText = 'go to'
|
||||
@@ -795,11 +811,14 @@
|
||||
let silde = createNumSlide(label,
|
||||
data.inputs.weight,
|
||||
(v) => {
|
||||
window._appData.data[data.id].inputs.weight = v;
|
||||
window._appData.data[data.id].inputs.weight = parseFloat(v);
|
||||
},
|
||||
options.min,
|
||||
options.max,
|
||||
'float')
|
||||
'float',
|
||||
options.keywords,
|
||||
data.id
|
||||
)
|
||||
container.appendChild(silde);
|
||||
}
|
||||
|
||||
@@ -884,7 +903,13 @@
|
||||
return container
|
||||
}
|
||||
|
||||
function createNumSlide(labelText, value = 0, callback, minValue = 0, maxValue = 1, type = 'float') {
|
||||
function createNumSlide(labelText, value = 0, callback, minValue = 0, maxValue = 1, type = 'float', keywords = null, targetId = null) {
|
||||
|
||||
value = 'float' ? parseFloat(value.toFixed(3)) : parseInt(value)
|
||||
|
||||
// 输入div
|
||||
let inputDiv = document.createElement('div');
|
||||
inputDiv.style.display = 'flex'
|
||||
|
||||
// 创建滑块输入元素
|
||||
var slider = document.createElement("input");
|
||||
@@ -893,17 +918,58 @@
|
||||
slider.max = maxValue;
|
||||
slider.step = type == 'float' ? 0.01 : 1
|
||||
slider.value = value;
|
||||
slider.style.width = '150px'
|
||||
|
||||
// 创建标签元素
|
||||
var label = document.createElement("label");
|
||||
label.innerHTML = labelText;
|
||||
label.setAttribute('data-content', value);
|
||||
|
||||
if (keywords && keywords[0]) {
|
||||
label.innerHTML = ``
|
||||
// 有备选的关键词
|
||||
let selectTag = createSelect(Array.from([labelText, ...keywords], k => {
|
||||
return {
|
||||
value: k,
|
||||
text: k,
|
||||
}
|
||||
}), labelText);
|
||||
selectTag.style=`background: none;
|
||||
color: black; max-width: 300px;
|
||||
border-bottom: 1px solid #acacac;
|
||||
border-radius: 0;`
|
||||
selectTag.addEventListener('change', e => {
|
||||
e.preventDefault();
|
||||
window._appData.data[targetId].inputs.prompt_keyword = selectTag.value;
|
||||
// label.querySelector('.label').innerText = selectTag.value
|
||||
// console.log(window._appData.data[targetId].inputs.prompt_keyword,selectTag.value)
|
||||
})
|
||||
label.appendChild(selectTag);
|
||||
}
|
||||
|
||||
// 创建容器元素,并将滑块输入和标签添加到容器中
|
||||
var container = document.createElement("div");
|
||||
container.appendChild(label);
|
||||
container.appendChild(slider);
|
||||
container.className = 'card'
|
||||
|
||||
container.className = 'card';
|
||||
|
||||
|
||||
// 创建切换按钮元素
|
||||
var toggleButton = document.createElement("button");
|
||||
toggleButton.innerHTML = `<svg style="width:24px;height: 24px;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="3972"><path d="M514 114.3c-219.9 0-398.9 178.9-398.9 398.8 0.1 220 179 398.9 398.9 398.9 219.9 0 398.8-178.9 398.8-398.8S733.9 114.3 514 114.3z m218.3 489v1.7c0 0.5-0.1 1-0.1 1.6 0 0.3 0 0.6-0.1 0.9 0 0.5-0.1 1-0.2 1.5 0 0.3-0.1 0.7-0.1 1-0.1 0.4-0.1 0.8-0.2 1.2-0.1 0.4-0.2 0.9-0.2 1.3-0.1 0.3-0.1 0.6-0.2 0.8-0.1 0.6-0.3 1.2-0.4 1.8 0 0.1-0.1 0.2-0.1 0.3-2.2 8.5-6.6 16.6-13.3 23.3L600.7 755.4c-20 20-52.7 20-72.6 0-20-20-20-52.7 0-72.6l28.9-28.9H347c-28.3 0-51.4-23.1-51.4-51.4 0-28.3 23.1-51.4 51.4-51.4h334c13.2 0 26.4 5 36.4 15s15 23.2 15 36.4c0 0.3-0.1 0.6-0.1 0.8z m0.1-179.5c0 28.3-23.1 51.4-51.4 51.4H347c-13.2 0-26.4-5-36.4-15s-15-23.2-15-36.4v-0.8-1.6c0-0.5 0.1-1.1 0.1-1.6 0-0.3 0-0.6 0.1-0.9 0-0.5 0.1-1 0.2-1.5 0-0.3 0.1-0.7 0.1-1 0.1-0.4 0.1-0.8 0.2-1.2 0.1-0.4 0.2-0.9 0.2-1.3 0.1-0.3 0.1-0.6 0.2-0.8 0.1-0.6 0.3-1.2 0.4-1.8 0-0.1 0.1-0.2 0.1-0.3 2.2-8.5 6.6-16.6 13.3-23.3l116.6-116.6c20-20 52.7-20 72.6 0 20 20 20 52.7 0 72.6L471 372.5h210c28.2 0 51.4 23.1 51.4 51.3z" fill="#0C0C0C" p-id="3973"></path></svg>`;
|
||||
toggleButton.style = `margin-left:12px;background:none;color: black;`
|
||||
|
||||
toggleButton.addEventListener("click", function () {
|
||||
if (slider.type === 'range') {
|
||||
slider.type = 'number'
|
||||
} else {
|
||||
slider.type = 'range'
|
||||
}
|
||||
});
|
||||
|
||||
inputDiv.appendChild(slider);
|
||||
inputDiv.appendChild(toggleButton);
|
||||
container.appendChild(inputDiv)
|
||||
|
||||
// 添加change事件监听器
|
||||
slider.addEventListener("input", function (event) {
|
||||
@@ -920,16 +986,7 @@
|
||||
|
||||
}
|
||||
|
||||
function createSelectWithOptions(title, options, defaultValue) {
|
||||
|
||||
const div = document.createElement("div");
|
||||
div.className = 'card';
|
||||
|
||||
// Create a label for the upload control
|
||||
const nameLabel = document.createElement("label");
|
||||
nameLabel.textContent = title;
|
||||
div.appendChild(nameLabel);
|
||||
|
||||
function createSelect(options, defaultValue) {
|
||||
var selectElement = document.createElement("select");
|
||||
selectElement.className = "select"
|
||||
|
||||
@@ -944,6 +1001,21 @@
|
||||
// 设置默认值
|
||||
selectElement.value = defaultValue;
|
||||
|
||||
return selectElement
|
||||
}
|
||||
|
||||
function createSelectWithOptions(title, options, defaultValue) {
|
||||
|
||||
const div = document.createElement("div");
|
||||
div.className = 'card';
|
||||
|
||||
// Create a label for the upload control
|
||||
const nameLabel = document.createElement("label");
|
||||
nameLabel.textContent = title;
|
||||
div.appendChild(nameLabel);
|
||||
|
||||
var selectElement = createSelect(options, defaultValue);
|
||||
|
||||
div.appendChild(selectElement)
|
||||
|
||||
return [div, selectElement];
|
||||
@@ -963,7 +1035,9 @@
|
||||
|
||||
|
||||
|
||||
function createUI(inputData, outputData, seed = {}, share = true, link = "") {
|
||||
function createUI(data, share = true) {
|
||||
// appData.input, appData.output, appData.seed, share, appData.link
|
||||
const { input: inputData, output: outputData, data: workflow, seed, link } = data;
|
||||
|
||||
let mainDiv = document.createElement('div');
|
||||
let leftDetails = document.createElement('details');
|
||||
@@ -1035,17 +1109,35 @@
|
||||
try {
|
||||
if (Object.keys(seed).length > 0) {
|
||||
seeds.innerHTML = `<summary>SEED</summary>
|
||||
<div class="content"> </div>`
|
||||
<div class="content"> </div>`;
|
||||
const content = seeds.querySelector('.content')
|
||||
for (const id in seed) {
|
||||
const s = seed[id];
|
||||
let em = document.createElement('em');
|
||||
em.innerText = s;
|
||||
seeds.querySelector('.content').appendChild(em)
|
||||
if (!Array.isArray(workflow[id].inputs.seed)) {
|
||||
|
||||
let seedInput = document.createElement('div');
|
||||
content.appendChild(seedInput)
|
||||
seedInput.style = `outline: 1px dashed gray;margin-bottom: 12px;margin-top: 12px;`
|
||||
|
||||
let em = document.createElement('em');
|
||||
em.innerText = `#${id} ${s.toUpperCase()}`;
|
||||
seedInput.appendChild(em)
|
||||
|
||||
if (s === 'fixed') {
|
||||
// console.log('###fiex',1)
|
||||
let inSeed = createNumSlide(``, 0, (newSeed) => updateSeed(id, newSeed), 0, 1849378600828930, 'int');
|
||||
inSeed.style = `padding: 8px;background: none;`
|
||||
seedInput.appendChild(inSeed)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
|
||||
console.log(error)
|
||||
}
|
||||
|
||||
|
||||
@@ -1205,7 +1297,7 @@
|
||||
async function createApp(appData, share = true) {
|
||||
// console.log(appData)
|
||||
// 使用示例:
|
||||
var ui = createUI(appData.input, appData.output, appData.seed, share,appData.link);
|
||||
var ui = createUI(appData, share);
|
||||
|
||||
// 更新标题
|
||||
ui.title.update(appData.name || 'Mixlab APP');
|
||||
@@ -1221,7 +1313,7 @@
|
||||
// 添加提交按钮点击事件
|
||||
ui.submitButton.update(function () {
|
||||
// 在提交按钮点击时执行的逻辑
|
||||
queuePrompt(window._appData.data, api.clientId)
|
||||
queuePrompt(window._appData.data, window._appData.seed, api.clientId)
|
||||
});
|
||||
|
||||
|
||||
|
||||
@@ -99,6 +99,12 @@ function extractInputAndOutputData (jsonData, inputIds = [], outputIds = []) {
|
||||
if (node.type == 'PromptSlide') {
|
||||
// min max step
|
||||
options = node.widgets.filter(w => w.type === 'slider')[0].options
|
||||
// 备选的keywords清单
|
||||
let keywords=getLocalData(`${id}_PromptSlide`);
|
||||
// console.log('keywords',keywords)
|
||||
if(keywords&&keywords[0]){
|
||||
options.keywords=keywords;
|
||||
}
|
||||
}
|
||||
|
||||
input[inputIds.indexOf(id)] = {
|
||||
@@ -136,6 +142,16 @@ function getUrl () {
|
||||
return url
|
||||
}
|
||||
|
||||
const getLocalData = key => {
|
||||
let data = {}
|
||||
try {
|
||||
data = JSON.parse(localStorage.getItem(key)) || {}
|
||||
} catch (error) {
|
||||
return {}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
async function save_app (json) {
|
||||
let url = getUrl()
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { app } from '../../../scripts/app.js'
|
||||
const repoOwner = 'shadowcz007' // 替换为仓库的所有者
|
||||
const repoName = 'comfyui-mixlab-nodes' // 替换为仓库的名称
|
||||
|
||||
const version = 'v0.8.0'
|
||||
const version = 'v0.9.0'
|
||||
|
||||
fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
|
||||
.then(response => response.json())
|
||||
|
||||
@@ -0,0 +1,199 @@
|
||||
import { app } from '../../../scripts/app.js'
|
||||
import { api } from '../../../scripts/api.js'
|
||||
import { ComfyWidgets } from '../../../scripts/widgets.js'
|
||||
import { $el } from '../../../scripts/ui.js'
|
||||
|
||||
function get_position_style (ctx, widget_width, y, node_height) {
|
||||
const MARGIN = 4 // the margin around the html element
|
||||
|
||||
/* Create a transform that deals with all the scrolling and zooming */
|
||||
const elRect = ctx.canvas.getBoundingClientRect()
|
||||
const transform = new DOMMatrix()
|
||||
.scaleSelf(
|
||||
elRect.width / ctx.canvas.width,
|
||||
elRect.height / ctx.canvas.height
|
||||
)
|
||||
.multiplySelf(ctx.getTransform())
|
||||
.translateSelf(MARGIN, MARGIN + y)
|
||||
|
||||
return {
|
||||
transformOrigin: '0 0',
|
||||
transform: transform,
|
||||
left: `0`,
|
||||
top: `0`,
|
||||
cursor: 'pointer',
|
||||
position: 'absolute',
|
||||
maxWidth: `${widget_width - MARGIN * 2}px`,
|
||||
// maxHeight: `${node_height - MARGIN * 2}px`, // we're assuming we have the whole height of the node
|
||||
width: `${widget_width - MARGIN * 2 - 24}px`,
|
||||
// height: `${node_height * 0.3 - MARGIN * 2}px`,
|
||||
// background: '#EEEEEE',
|
||||
paddingLeft: '12px',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
// alignItems: 'center',
|
||||
justifyContent: 'space-between'
|
||||
}
|
||||
}
|
||||
|
||||
const getLocalData = key => {
|
||||
let data = {}
|
||||
try {
|
||||
data = JSON.parse(localStorage.getItem(key)) || {}
|
||||
} catch (error) {
|
||||
return {}
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
const setLocalDataOfWin = (key, value) => {
|
||||
localStorage.setItem(key, JSON.stringify(value))
|
||||
// window[key] = value
|
||||
}
|
||||
|
||||
const createSelect = (select, opts, targetWidget) => {
|
||||
select.style.display = 'block'
|
||||
let html = ''
|
||||
let isMatch = false
|
||||
for (const opt of opts) {
|
||||
html += `<option value='${opt}' ${
|
||||
targetWidget.value === opt ? 'selected' : ''
|
||||
}>${opt}</option>`
|
||||
if (targetWidget.value === opt) isMatch = true
|
||||
}
|
||||
select.innerHTML = html
|
||||
if (!isMatch) targetWidget.value = opts[0]
|
||||
// 添加change事件监听器
|
||||
select.addEventListener('change', function () {
|
||||
// 获取选中的选项的值
|
||||
var selectedOption = select.options[select.selectedIndex].value
|
||||
targetWidget.value = selectedOption
|
||||
// console.log(widget,selectedOption)
|
||||
})
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.prompt.PromptSlide',
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeType.comfyClass == 'PromptSlide') {
|
||||
const orig_nodeCreated = nodeType.prototype.onNodeCreated
|
||||
nodeType.prototype.onNodeCreated = async function () {
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
|
||||
const prompt_keyword = this.widgets.filter(
|
||||
w => w.name == 'prompt_keyword'
|
||||
)[0]
|
||||
// console.log('PromptSlide nodeData', prompt_keyword)
|
||||
|
||||
const widget = {
|
||||
type: 'div',
|
||||
name: 'upload',
|
||||
draw (ctx, node, widget_width, y, widget_height) {
|
||||
Object.assign(
|
||||
this.div.style,
|
||||
get_position_style(ctx, widget_width, y, node.size[1])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
widget.div = $el('div', {})
|
||||
|
||||
const btn = document.createElement('button')
|
||||
btn.innerText = 'Upload Keywords'
|
||||
|
||||
btn.style = `cursor: pointer;
|
||||
font-weight: 300;
|
||||
margin: 2px;
|
||||
color: var(--descrip-text);
|
||||
background-color: var(--comfy-input-bg);
|
||||
border-radius: 8px;
|
||||
border-color: var(--border-color);
|
||||
border-style: solid; height: 30px;min-width: 122px;
|
||||
`
|
||||
|
||||
const select = document.createElement('select')
|
||||
select.style = `display:none;cursor: pointer;
|
||||
font-weight: 300;
|
||||
margin: 2px;
|
||||
color: var(--descrip-text);
|
||||
background-color: var(--comfy-input-bg);
|
||||
border-radius: 8px;
|
||||
border-color: var(--border-color);
|
||||
border-style: solid; height: 30px;min-width: 100px;
|
||||
`
|
||||
widget.select = select
|
||||
|
||||
// const btn=document.createElement('button');
|
||||
// btn.innerText='Upload'
|
||||
btn.addEventListener('click', () => {
|
||||
let inp = document.createElement('input')
|
||||
inp.type = 'file'
|
||||
inp.accept = '.txt'
|
||||
inp.click()
|
||||
inp.addEventListener('change', event => {
|
||||
// 获取选择的文件
|
||||
const file = event.target.files[0]
|
||||
|
||||
// 创建文件读取器
|
||||
const reader = new FileReader()
|
||||
|
||||
// 定义读取完成事件的回调函数
|
||||
reader.onload = (event)=> {
|
||||
// 读取完成后的文本内容
|
||||
const fileContent = event.target.result.split('\n')
|
||||
const keywords = Array.from(fileContent, f => f.trim()).filter(
|
||||
f => f
|
||||
)
|
||||
// 打印文件内容
|
||||
// console.log(keywords)
|
||||
|
||||
// widget.value = keywords
|
||||
setLocalDataOfWin(`${this.id}_PromptSlide`,keywords)
|
||||
|
||||
createSelect(select, keywords, prompt_keyword)
|
||||
|
||||
inp.remove()
|
||||
}
|
||||
|
||||
// 以文本方式读取文件
|
||||
reader.readAsText(file)
|
||||
})
|
||||
})
|
||||
|
||||
widget.div.appendChild(btn)
|
||||
widget.div.appendChild(select)
|
||||
document.body.appendChild(widget.div)
|
||||
this.addCustomWidget(widget)
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
this.onRemoved = () => {
|
||||
widget.div.remove()
|
||||
return onRemoved?.()
|
||||
}
|
||||
|
||||
if (this.onResize) {
|
||||
this.onResize(this.size)
|
||||
}
|
||||
|
||||
this.serialize_widgets = true //需要保存参数
|
||||
}
|
||||
}
|
||||
},
|
||||
async loadedGraphNode (node, app) {
|
||||
if (node.type === 'PromptSlide') {
|
||||
try {
|
||||
let prompt = node.widgets.filter(w => w.name === 'prompt_keyword')[0]
|
||||
|
||||
let keywords= getLocalData( `${node.id}_PromptSlide`)
|
||||
// console.log('keywords',keywords)
|
||||
let widget = node.widgets.filter(w => w.select)[0]
|
||||
if (keywords && keywords[0]) {
|
||||
// let widget = node.widgets.filter(w => w.select)[0]
|
||||
// console.log('select',widget,widget.value)
|
||||
widget.select.style.display = 'block'
|
||||
createSelect(widget.select, keywords, prompt)
|
||||
}
|
||||
} catch (error) {}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -808,7 +808,9 @@ app.registerExtension({
|
||||
d.style = `text-align: left;margin:6px;color: var(--input-text);
|
||||
background-color: var(--comfy-input-bg); border-color: var(--border-color);cursor: pointer;`
|
||||
d.addEventListener('click', () => {
|
||||
console.log('node')
|
||||
const node = app.graph.getNodeById(nodeId)
|
||||
|
||||
if (!node) return
|
||||
app.canvas.centerOnNode(node)
|
||||
app.canvas.setZoom(1)
|
||||
@@ -844,6 +846,7 @@ app.registerExtension({
|
||||
d.style = `text-align: left;margin:6px;color: var(--input-text);
|
||||
background-color: var(--comfy-input-bg); border-color: var(--border-color);cursor: pointer;`
|
||||
d.addEventListener('click', () => {
|
||||
console.log('click')
|
||||
const node = app.graph.getNodeById(nodeId)
|
||||
if (!node) return
|
||||
app.canvas.centerOnNode(node)
|
||||
@@ -905,15 +908,6 @@ app.registerExtension({
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
|
||||
// options.push({
|
||||
// content: `Save For App ♾️Mixlab`,
|
||||
// disabled: false, // or a function determining whether to disable
|
||||
// callback: async () => {
|
||||
|
||||
// }
|
||||
// })
|
||||
return options
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 1.2 MiB |
Reference in New Issue
Block a user