Compare commits

...
76 Commits
Author SHA1 Message Date
shadowcz007 812879610a v0.14.0
发布新节点splitImage & gridoutput,用于分割图片和随机摆放元素
修复若干bug
2024-01-20 21:43:18 +08:00
shadowcz007 c5e521ccc1 add splitImage&gridoutput 2024-01-20 17:39:53 +08:00
shadowcz007 bc1c8fa351 Update index.html 2024-01-19 09:45:03 +08:00
shadowcz007 7271fcf9c1 api 2024-01-18 22:49:13 +08:00
shadowcz007 ace3b7707b Update README.md 2024-01-18 12:45:29 +08:00
shadowcz007 9eb65cc4ee Update ClipInterrogator.py 2024-01-18 11:12:03 +08:00
shadowcz007 c5c2bc779c add JoinWithDelimiter 2024-01-18 11:00:37 +08:00
shadowcz007 ae1751d9c0 Update Utils.py 2024-01-18 00:32:12 +08:00
shadowcz007 d17583ef7d fixbug 2024-01-17 17:56:23 +08:00
shadowcz007 a363713ae0 v0.13.0
EmbeddingPrompt & 修复若干bug
2024-01-16 23:17:56 +08:00
shadowcz007 1566165bd4 Create space.txt 2024-01-16 17:50:51 +08:00
shadowcz007 fe065fa318 OutlineMask for inpaint 2024-01-16 10:54:26 +08:00
shadowcz007 202d5cf071 支持富文本定义跳转按钮 2024-01-15 14:21:39 +08:00
shadowcz007 b785a9dc5b add EmbeddingPrompt 2024-01-15 13:02:26 +08:00
shadowcz007 73bc658b2f 修复 sentencepiece 未安装的bug 2024-01-15 08:40:07 +08:00
shadowcz007 0b94216138 Update ui_mixlab.js 2024-01-14 20:35:27 +08:00
shadowcz007 86eec2b4cc Update ui_mixlab.js 2024-01-14 20:34:07 +08:00
shadowcz007 c99b531d28 Update ui_mixlab.js 2024-01-14 20:33:53 +08:00
shadowcz007 74a4338cb5 Merge branch 'main' of https://github.com/shadowcz007/comfyui-mixlab-nodes 2024-01-14 13:14:42 +08:00
shadowcz007 d691c52e49 Update TextGenerateNode.py 2024-01-14 13:12:10 +08:00
gold3bear bd3e9e4b3c 去掉调试数据 2024-01-14 12:31:05 +08:00
shadow 3c3ca5fb9c Merge pull request #138 from shadowcz007/fix-chinese-prompt
Fix chinese prompt
2024-01-14 11:58:08 +08:00
shadowcz007 e4ff4fce1c update 2024-01-14 11:57:18 +08:00
gold3bear 2918d4b07d correct Chinese text to prompt syntax 2024-01-14 11:50:14 +08:00
gold3bear c40e49be46 fix chinese prompt 2024-01-14 11:34:58 +08:00
shadowcz007 4ccda20975 add rembg 2024-01-14 11:06:46 +08:00
shadowcz007 09957617d3 修复SwitchByIndex的bug 2024-01-13 20:59:29 +08:00
shadowcz007 c703aa7058 中文prompt增加选项,可控制是否添加更多 2024-01-13 20:59:07 +08:00
shadowcz007 64d366d323 Update prompt_mixlab.js 2024-01-13 17:12:03 +08:00
shadowcz007 333e0a2faa Update ImageNode.py 2024-01-13 16:32:51 +08:00
shadowcz007 a677d95bc8 Update README.md 2024-01-13 16:02:24 +08:00
shadowcz007 aafd87e84b v0.12.0 ChinesePrompt && PromptGenerate
> ChinesePrompt && PromptGenerate,中文prompt节点,直接用中文书写你的prompt

![](./assets/ChinesePrompt_workflow.svg)

> Web App增加图片编辑器
2024-01-13 15:59:52 +08:00
shadowcz007 efa3bae54b Create profession.txt 2024-01-13 12:25:05 +08:00
shadowcz007 8e4362689d Lama、ClipInterrogator安装移到节点内 2024-01-13 12:11:46 +08:00
shadowcz007 b86634284e appinfo运行bug 2024-01-13 11:34:17 +08:00
shadowcz007 b3293ddccd 优化promptImage的预览 2024-01-12 17:16:47 +08:00
shadowcz007 4b40831b83 prompt keywords 2024-01-11 23:05:25 +08:00
shadowcz007 e426b0521c 添加图片编辑功能 2024-01-11 23:01:58 +08:00
shadowcz007 1777bf6e06 修复切换workflow,数据未清空的情况 2024-01-11 23:01:42 +08:00
shadowcz007 dafc892f0f add image edit for web app 2024-01-11 15:41:05 +08:00
shadowcz007 fea0cfd5dd 更新workflow示例 2024-01-11 15:40:31 +08:00
shadowcz007 a7e158db6d update workflow example 2024-01-11 15:32:20 +08:00
shadowcz007 455ac4abd3 Update promptslide-appinfo-workflow.svg 2024-01-11 15:27:11 +08:00
shadowcz007 778dfa2cf5 update workflow example 2024-01-11 15:24:31 +08:00
shadowcz007 329f2e6f81 系统字体的获取 2024-01-10 16:19:49 +08:00
shadowcz007 63ad6d97d7 Update ImageNode.py 2024-01-09 22:59:34 +08:00
shadowcz007 8db56db7cf Update ImageNode.py 2024-01-09 22:34:13 +08:00
shadowcz007 bd542f1e0b Update app_mixlab.js 2024-01-09 18:42:36 +08:00
shadowcz007 a162e53dea Update ImageNode.py 2024-01-09 18:05:18 +08:00
shadowcz007 a8a4c848ed Update __init__.py 2024-01-09 16:15:15 +08:00
shadowcz007 bddd38996a Update ImageNode.py 2024-01-09 15:16:38 +08:00
shadowcz007 9f084eae94 修复LoadImagesFromPath的bug 2024-01-09 14:33:13 +08:00
shadowcz007 c54c635161 v0.11.4 2024-01-09 12:59:36 +08:00
shadowcz007 fa8b42e05e 增强ImageCrop功能 2024-01-09 12:48:20 +08:00
shadowcz007 9c3c323884 fixbug: user_manager 2024-01-09 12:41:29 +08:00
shadowcz007 c8a46439be v0.11.3 - 修复appinfo的logo输入 2024-01-09 09:17:00 +08:00
shadowcz007 b7ec701259 Update Utils.py 2024-01-09 09:16:15 +08:00
shadowcz007 a2cd0e0a38 v0.11.2
- 优化appinfo - 自动保存
- 优化mergeLayer,可以输出合成的mask
- 添加一个实验性的节点 ImageColorTransfer
- random prompt ,增加上传关键词功能
- 修复LoadImageFromPath的bug
2024-01-08 23:03:37 +08:00
shadowcz007 7bccc0e236 优化appinfo-不需要输出,每次运行都会自动更新数据 2024-01-08 23:01:31 +08:00
shadowcz007 a3a649a79f 修复 LoadImagesFromPath 不更新的bug 2024-01-08 22:11:10 +08:00
shadowcz007 2a14d30552 添加一个实验性的节点 ImageColorTransfer 2024-01-08 16:40:00 +08:00
shadowcz007 313bef0609 ClipInterrogator优化 2024-01-07 22:55:24 +08:00
shadowcz007 960a80aeca Merge branch 'main' of https://github.com/shadowcz007/comfyui-mixlab-nodes 2024-01-07 19:59:24 +08:00
shadowcz007 20e6d50a98 修复bug 2024-01-07 19:59:22 +08:00
shadow 507c3417d6 Merge pull request #114 from shadowcz007/fix_ssl_prot_occupied
fix:ssl prot occupied
2024-01-07 17:08:08 +08:00
gold3bear d1adb8d4ed fix:ssl prot occupied 2024-01-07 15:56:23 +08:00
shadowcz007 915ff12747 支持图片的batch 2024-01-06 23:20:43 +08:00
shadowcz007 fbade79137 Update utils_mixlab.js 2024-01-06 20:15:17 +08:00
shadowcz007 a240a677d0 Update utils_mixlab.js 2024-01-06 20:14:17 +08:00
shadowcz007 ab64cf31f6 FloatSlider 优化 2024-01-06 20:13:00 +08:00
shadowcz007 67f2e32dae floatSlider 优化 2024-01-06 20:00:58 +08:00
shadowcz007 0dc40fe052 修复bug 2024-01-06 17:55:26 +08:00
shadowcz007 b7225be552 Update ImageNode.py 2024-01-06 16:25:35 +08:00
shadowcz007 629e00ec94 fixbug 2024-01-06 12:14:02 +08:00
shadowcz007 6d033c9314 random prompt ,增加上传关键词功能 2024-01-06 08:40:27 +08:00
shadowcz007 4f8926ed00 promptslide 上传txt后写入workflow保留列表数据 2024-01-06 08:12:59 +08:00
39 changed files with 4299 additions and 1209 deletions
+14 -17
View File
@@ -31,7 +31,7 @@ APP-JSON:
> 暂时支持8种节点作为界面上的输入节点:Load Image、CLIPTextEncode、PromptSlide、TextInput_、Color、FloatSlider、IntNumber、CheckpointLoaderSimple、LoraLoader
> 输出节点:PreviewImage 、SaveImage、ShowTextForGPT、VHS_VideoCombine
> 输出节点:PreviewImage 、SaveImage、ShowTextForGPT、VHS_VideoCombine、PromptImage
> seed统一输入控件,支持:SamplerCustom、KSampler
@@ -67,7 +67,7 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
> PromptSlide
![](./assets/prompt_weight.png)
![](./workflow/promptslide-appinfo-workflow.svg)
<!-- ![](./workflow/promptslide-appinfo-workflow.svg) -->
> randomPrompt
@@ -79,6 +79,10 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
> PromptImage & PromptSimplification,Assist in simplifying prompt words, comparing images and prompt word nodes.
> ChinesePrompt && PromptGenerate,中文prompt节点,直接用中文书写你的prompt
![](./assets/ChinesePrompt_workflow.svg)
### Layers
> A new layer class node has been added, allowing you to separate the image into layers. After merging the images, you can input the controlnet for further processing.
@@ -159,23 +163,19 @@ An improvement has been made to directly redirect to GitHub to search for missin
![node-not-found](./assets/node-not-found.png)
### Update
v0.8.0 🚀🚗🚚🏃‍ LaMaInpainting
- 新增 LaMaInpainting
- 优化color节点的输出
- 修复高清显示屏上定位节点不准的情况
- Add LaMaInpainting
- Optimize the output of the color node
- Fix the issue of inaccurate positioning node on high-definition display screens
### Models
[Download rembg Models](https://github.com/danielgatis/rembg/tree/main#Models),move to:models/rembg
[Download CLIPSeg](https://huggingface.co/CIDAS/clipseg-rd64-refined/tree/main), move to : models/clipseg
[Download lama](https://github.com/enesmsahin/simple-lama-inpainting/releases/download/v0.1.0/big-lama.pt), move to : models/lama
[Download Salesforce\blip-image-captioning-base](https://huggingface.co/Salesforce/blip-image-captioning-base), move to : models/clip_interrogator/Salesforce/blip-image-captioning-base
[Download Salesforce/blip-image-captioning-base](https://huggingface.co/Salesforce/blip-image-captioning-base), move to : models/clip_interrogator/Salesforce/blip-image-captioning-base
[Download succinctly/text2image-prompt-generator](https://huggingface.co/succinctly/text2image-prompt-generator/tree/main),move to:prompt_generator/text2image-prompt-generator
[Download Helsinki-NLP/opus-mt-zh-en](https://huggingface.co/Helsinki-NLP/opus-mt-zh-en/tree/main),move to:prompt_generator/opus-mt-zh-en
## Installation
@@ -216,9 +216,6 @@ pip3 install -r requirements.txt
#### discussions:
[discussions](https://github.com/shadowcz007/comfyui-mixlab-nodes/discussions)
### TODO:
- 音频播放节点:带可视化、支持多音轨、可配置音轨音量
- vector https://github.com/GeorgLegato/stable-diffusion-webui-vectorstudio
<picture>
+113 -21
View File
@@ -192,7 +192,7 @@ def read_workflow_json_files(folder_path ):
def get_workflows():
# print("#####path::", current_path)
workflow_path=os.path.join(current_path, "workflow")
print('workflow_path: ',workflow_path)
# print('workflow_path: ',workflow_path)
if not os.path.exists(workflow_path):
# 使用mkdir()方法创建新目录
os.mkdir(workflow_path)
@@ -231,8 +231,14 @@ def get_my_workflow_for_app(filename="my_workflow_app.json",category="",is_all=F
})
else:
category=''
input=None
output=None
if 'category' in x['app']:
category=x['app']['category']
if 'input' in x['app']:
input=x['app']['input']
if 'output' in x['app']:
output=x['app']['output']
apps.append({
"filename":item["filename"],
"category":category,
@@ -244,6 +250,8 @@ def get_my_workflow_for_app(filename="my_workflow_app.json",category="",is_all=F
"icon":(x['app']['icon'] if 'icon' in x['app'] else None),
"name":x['app']['name'],
"version":x['app']['version'],
"input":input,
"output":output
}
},
"date":item["date"]
@@ -271,8 +279,14 @@ def get_my_workflow_for_app(filename="my_workflow_app.json",category="",is_all=F
# print(apps[0]['filename'] ,item["filename"])
if apps[0]['filename']!=item["filename"]:
category=''
input=None
output=None
if 'category' in x['app']:
category=x['app']['category']
if 'input' in x['app']:
input=x['app']['input']
if 'output' in x['app']:
output=x['app']['output']
apps.append({
"filename":item["filename"],
# "category":category,
@@ -284,6 +298,8 @@ def get_my_workflow_for_app(filename="my_workflow_app.json",category="",is_all=F
"icon":(x['app']['icon'] if 'icon' in x['app'] else None),
"name":x['app']['name'],
"version":x['app']['version'],
"input":input,
"output":output
}
},
"date":item["date"]
@@ -351,33 +367,59 @@ async def new_request(self, method, url, *args, **kwargs):
# 应用 Monkey Patch
aiohttp.ClientSession._request = new_request
import socket
async def check_port_available(address, port):
#检查端口是否可用
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
sock.bind((address, port))
return True
except socket.error:
return False
# https
async def new_start(self, address, port, verbose=True, call_on_start=None):
try:
runner = web.AppRunner(self.app, access_log=None)
await runner.setup()
if not await check_port_available(address, port):
raise RuntimeError(f"Port {port} is already in use.")
site = web.TCPSite(runner, address, port)
await site.start()
import ssl
crt,key=create_for_https()
crt, key = create_for_https()
ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ssl_context.load_cert_chain(crt,key)
site2 = web.TCPSite(runner, address, port+1,ssl_context=ssl_context)
await site2.start()
ssl_context.load_cert_chain(crt, key)
success = False
for i in range(10): # 尝试最多10次
if await check_port_available(address, port + 1 + i):
https_port = port + 1 + i
site2 = web.TCPSite(runner, address, https_port, ssl_context=ssl_context)
await site2.start()
success = True
break
if not success:
raise RuntimeError(f"Ports {port + 1} to {port + 10} are all in use.")
if address == '':
address = '0.0.0.0'
if verbose:
# print('\033[91mMixlab Nodes: \033[93mLoaded\033[0m')
print("\033[93mStarting server\n")
print("\033[93mTo see the GUI go to: http://{}:{}".format(address, port))
print("\033[93mTo see the GUI go to: https://{}:{}\033[0m".format(address, port+1))
print("\033[93mTo see the GUI go to: https://{}:{}\033[0m".format(address, https_port))
if call_on_start is not None:
call_on_start(address, port)
except Exception as e:
print(f"Error starting the server: {e}")
# import webbrowser
# if os.name == 'nt' and address == '0.0.0.0':
# address = '127.0.0.1'
@@ -477,6 +519,11 @@ async def nodes_map_hander(request):
# 把插件自定义的路由添加到comfyui server里
def new_add_routes(self):
import nodes
try:
self.user_manager.add_routes(self.routes)
except:
print('pls update')
self.app.add_routes(routes)
self.app.add_routes(self.routes)
for name, dir in nodes.EXTENSION_WEB_DIRS.items():
@@ -503,26 +550,28 @@ PromptServer.add_routes=new_add_routes
# 导入节点
from .nodes.PromptNode import RandomPrompt,PromptSlide,PromptSimplification,PromptImage
from .nodes.ImageNode import NoiseImage,TransparentImage,GradientImage,LoadImagesFromPath,LoadImagesFromURL,ResizeImage,TextImage,SvgImage,Image3D,ShowLayer,NewLayer,MergeLayers,AreaToMask,SmoothMask,FeatheredMask,SplitLongMask,ImageCropByAlpha,EnhanceImage,FaceToMask
from .nodes.PromptNode import EmbeddingPrompt,RandomPrompt,PromptSlide,PromptSimplification,PromptImage,JoinWithDelimiter
from .nodes.ImageNode import SplitImage,GridOutput,GetImageSize_,MirroredImage,ImageColorTransfer,NoiseImage,TransparentImage,GradientImage,LoadImagesFromPath,LoadImagesFromURL,ResizeImage,TextImage,SvgImage,Image3D,ShowLayer,NewLayer,MergeLayers,AreaToMask,SmoothMask,SplitLongMask,ImageCropByAlpha,EnhanceImage,FaceToMask
from .nodes.Vae import VAELoader,VAEDecode
from .nodes.ScreenShareNode import ScreenShareNode,FloatingVideo
from .nodes.Clipseg import CLIPSeg,CombineMasks
from .nodes.ChatGPT import ChatGPTNode,ShowTextForGPT,CharacterInText
from .nodes.Audio import GamePal,SpeechRecognition,SpeechSynthesis
from .nodes.Utils import AppInfo,IntNumber,FloatSlider,TextInput,ColorInput,FontInput,TextToNumber,DynamicDelayProcessor,LimitNumber,SwitchByIndex,GetImageSize_,MultiplicationNode
from .nodes.Lama import LaMaInpainting
from .nodes.ClipInterrogator import ClipInterrogator
from .nodes.Utils import TESTNODE_,AppInfo,IntNumber,FloatSlider,TextInput,ColorInput,FontInput,TextToNumber,DynamicDelayProcessor,LimitNumber,SwitchByIndex,MultiplicationNode
from .nodes.Mask import OutlineMask,FeatheredMask
# 要导出的所有节点及其名称的字典
# 注意:名称应全局唯一
NODE_CLASS_MAPPINGS = {
"AppInfo":AppInfo,
"TESTNODE_":TESTNODE_,
"RandomPrompt":RandomPrompt,
"EmbeddingPrompt":EmbeddingPrompt,
"PromptSlide":PromptSlide,
"PromptSimplification":PromptSimplification,
"PromptImage":PromptImage,
"ClipInterrogator":ClipInterrogator,
"MirroredImage":MirroredImage,
"NoiseImage":NoiseImage,
"GradientImage":GradientImage,
"TransparentImage":TransparentImage,
@@ -533,8 +582,11 @@ NODE_CLASS_MAPPINGS = {
"EnhanceImage":EnhanceImage,
"SvgImage":SvgImage,
"3DImage":Image3D,
"ImageColorTransfer":ImageColorTransfer,
"ShowLayer":ShowLayer,
"NewLayer":NewLayer,
"SplitImage":SplitImage,
"GridOutput":GridOutput,
"MergeLayers":MergeLayers,
"SplitLongMask":SplitLongMask,
"FeatheredMask":FeatheredMask,
@@ -564,7 +616,9 @@ NODE_CLASS_MAPPINGS = {
"GetImageSize_":GetImageSize_,
"SwitchByIndex":SwitchByIndex,
"LimitNumber":LimitNumber,
"LaMaInpainting":LaMaInpainting
"OutlineMask":OutlineMask,
"JoinWithDelimiter":JoinWithDelimiter
# "LaMaInpainting":LaMaInpainting
# "GamePal":GamePal
}
@@ -586,14 +640,52 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"3DImage":"3DImage ♾️Mixlab",
"DynamicDelayProcessor":"DynamicDelayByText ♾️Mixlab",
"LaMaInpainting":"LaMaInpainting ♾️Mixlab",
"PromptSlide":"PromptSlide ♾️Mixlab"
# "GamePal":"GamePal ♾️Mixlab"
"PromptSlide":"PromptSlide ♾️Mixlab",
"PromptGenerate_Mix":"PromptGenerate ♾️Mixlab",
"ChinesePrompt_Mix":"ChinesePrompt ♾️Mixlab",
"GamePal":"GamePal ♾️Mixlab",
"RembgNode_Mix":"Removebg"
}
# web ui的节点功能
WEB_DIRECTORY = "./web"
print('--------------')
print('\033[91m ### Mixlab Nodes: \033[93mLoaded\033[0m')
print('--------------')
print('\033[91m ### Mixlab Nodes: \033[93mLoaded')
try:
from .nodes.Lama import LaMaInpainting
print('LaMaInpainting.available',LaMaInpainting.available)
if LaMaInpainting.available:
NODE_CLASS_MAPPINGS['LaMaInpainting']=LaMaInpainting
except Exception as e:
print('LaMaInpainting.available',False,e)
try:
from .nodes.ClipInterrogator import ClipInterrogator
print('ClipInterrogator.available',ClipInterrogator.available)
if ClipInterrogator.available:
NODE_CLASS_MAPPINGS['ClipInterrogator']=ClipInterrogator
except Exception as e:
print('ClipInterrogator.available',False,e)
try:
from .nodes.TextGenerateNode import PromptGenerate,ChinesePrompt
print('PromptGenerate.available',PromptGenerate.available)
if PromptGenerate.available:
NODE_CLASS_MAPPINGS['PromptGenerate_Mix']=PromptGenerate
print('ChinesePrompt.available',ChinesePrompt.available)
if ChinesePrompt.available:
NODE_CLASS_MAPPINGS['ChinesePrompt_Mix']=ChinesePrompt
except Exception as e:
print('TextGenerateNode.available',False,e)
try:
from .nodes.RembgNode import RembgNode_
print('RembgNode_.available',RembgNode_.available)
if RembgNode_.available:
NODE_CLASS_MAPPINGS['RembgNode_Mix']=RembgNode_
except Exception as e:
print('RembgNode_.available',False,e)
print('\033[93m -------------- \033[0m')
File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 2.4 MiB

+30
View File
@@ -0,0 +1,30 @@
Jony Ive
Dieter Rams
Philippe Starck
Karim Rashid
Yves Béhar
Marc Newson
Naoto Fukasawa
Jonathan Adler
Patricia Urquiola
Ross Lovegrove
Tom Dixon
Jasper Morrison
Charles Eames
Ray Eames
Achille Castiglioni
Ron Arad
Konstantin Grcic
Marcel Wanders
Maarten Baas
Stefan Sagmeister
Ingo Maurer
Hella Jongerius
Sam Hecht
Kim Colin
Jaime Hayon
Michael Anastassiades
Nendo
Oki Sato
Matali Crasset
Tokujin Yoshioka
+30
View File
@@ -0,0 +1,30 @@
Elegant evening gown
Casual jeans and t-shirt
Formal black suit
Stylish leather jacket
Flowy bohemian dress
Sporty tracksuit
Chic little black dress
Trendy ripped jeans
Classic white button-down shirt
Cozy oversized sweater
Sophisticated tailored blazer
Quirky patterned leggings
Striped sailor top
Polished knee-length skirt
Vintage-inspired floral dress
Edgy motorcycle jacket
Preppy polo shirt
Boho maxi skirt
Professional pinstripe suit
Relaxed denim shorts
Glamorous sequined dress
Athletic running shoes
Formal bow tie
Casual baseball cap
Stylish fedora hat
Warm woolen scarf
Comfortable cotton socks
Trendy ankle boots
Cute summer sandals
Cozy pajama set
+30
View File
@@ -0,0 +1,30 @@
Happy
Sad
Angry
Surprised
Excited
Worried
Confused
Disgusted
Amused
Bored
Curious
Embarrassed
Frustrated
Nervous
Pleased
Relieved
Shy
Tired
Serious
Silly
Proud
Grumpy
Smug
Sarcastic
Flirty
Skeptical
Shocked
Blissful
Envious
Mischievous
+6
View File
@@ -4761,12 +4761,18 @@
],
"https://github.com/shadowcz007/comfyui-mixlab-nodes": [
[
"GridOutput",
"SplitImage",
"PromptGenerate_Mix",
"JoinWithDelimiter",
"ChinesePrompt_Mix",
"3DImage",
"AppInfo",
"IntNumber",
"FloatSlider",
"ResizeImage",
"NoiseImage",
"PromptImage",
"AreaToMask",
"CLIPSeg_",
"CharacterInText",
+101
View File
@@ -0,0 +1,101 @@
Doctor
Teacher
Engineer
Lawyer
Accountant
Nurse
Architect
Chef
Pilot
Scientist
Artist
Writer
Musician
Actor
Photographer
Police officer
Firefighter
Dentist
Pharmacist
Veterinarian
Electrician
Plumber
Carpenter
Mechanic
Farmer
Astronaut
Athlete
Journalist
Politician
Economist
Psychologist
Social worker
Librarian
Translator
Salesperson
Entrepreneur
Financial advisor
Graphic designer
Web developer
Marketing manager
Human resources manager
Project manager
Event planner
Fashion designer
Interior decorator
Real estate agent
Archaeologist
Biologist
Chemist
Geologist
Physicist
Mathematician
Historian
Geographer
Economist
Sociologist
Anthropologist
Archaeologist
Linguist
Philosopher
Economist
Sociologist
Anthropologist
Archaeologist
Linguist
Philosopher
Geographer
Historian
Economist
Sociologist
Anthropologist
Archaeologist
Linguist
Philosopher
Geographer
Historian
Economist
Sociologist
Anthropologist
Archaeologist
Linguist
Philosopher
Geographer
Historian
Economist
Sociologist
Anthropologist
Archaeologist
Linguist
Philosopher
Geographer
Historian
Economist
Sociologist
Anthropologist
Archaeologist
Linguist
Philosopher
Geographer
Historian
#MixCopilot
+58
View File
@@ -0,0 +1,58 @@
Residential space
Apartment building
Villa
Bungalow
Condominium
Commercial space
Shopping mall
Supermarket
Restaurant
Store
Market
Office space
Office building
Office
Meeting room
Co-working space
Educational space
School
University
Training institution
Library
Laboratory
Medical space
Hospital
Clinic
Pharmacy
Nursing home
Rehabilitation center
Cultural space
Museum
Library
Theater
Concert hall
Gallery
Sports space
Sports stadium
Gym
Swimming pool
Basketball court
Football field
Transportation space
Airport
Train station
Subway station
Bus stop
Parking lot
Public space
Park
Square
Street
Pedestrian street
Community center
Industrial space
Factory
Warehouse
Production workshop
Mine
Power plant
+3 -3
View File
@@ -24,7 +24,7 @@ class SpeechRecognition:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/audio"
CATEGORY = "♾️Mixlab/Audio"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
@@ -48,7 +48,7 @@ class SpeechSynthesis:
OUTPUT_NODE = True
OUTPUT_IS_LIST = (True,)
CATEGORY = "♾️Mixlab/audio"
CATEGORY = "♾️Mixlab/Audio"
def run(self, text):
# print(session_history)
@@ -82,7 +82,7 @@ class GamePal:
OUTPUT_NODE = True
OUTPUT_IS_LIST = (False,)
CATEGORY = "♾️Mixlab/audio"
CATEGORY = "♾️Mixlab/Audio"
def run(self, input_text,input_num,python_code):
exec(python_code)
+127 -10
View File
@@ -1,15 +1,61 @@
import os
import os,sys
import folder_paths
from PIL import Image
import importlib.util
import comfy.utils
import numpy as np
import json
import torch
import random
# from clip_interrogator import Config, Interrogator
global _available
_available=False
def is_installed(package):
try:
spec = importlib.util.find_spec(package)
except ModuleNotFoundError:
return False
return spec is not None
try:
if is_installed('clip_interrogator')==False:
import subprocess
# 安装
print('#pip install clip-interrogator==0.6.0')
result = subprocess.run([sys.executable, '-s', '-m', 'pip', 'install', 'clip-interrogator==0.6.0'], capture_output=True, text=True)
#检查命令执行结果
if result.returncode == 0:
print("#install success")
from clip_interrogator import Config, Interrogator
_available=True
else:
print("#install error")
else:
from clip_interrogator import Config, Interrogator
_available=True
except:
_available=False
try:
from transformers import AutoProcessor, BlipForConditionalGeneration
except:
_available=False
print('pls check transformers.__version__>=4.36.0:: AutoProcessor, BlipForConditionalGeneration')
from transformers import AutoProcessor, BlipForConditionalGeneration
from clip_interrogator import Config, Interrogator
def load_caption_model(model_path,config,t='blip-base'):
dtype=torch.float16 if config.device == 'cuda' else torch.float32
@@ -28,11 +74,11 @@ def load_caption_model(model_path,config,t='blip-base'):
caption_model_path=os.path.join(folder_paths.models_dir, "clip_interrogator/Salesforce/blip-image-captioning-base")
if not os.path.exists(caption_model_path):
print(f"## clip_interrogator_model not found: {caption_model_path}, pls download from https://huggingface.co/Salesforce/blip-image-captioning-base")
caption_model_path='Salesforce/blip-image-captioning-base'
cache_path=os.path.join(folder_paths.models_dir, "clip_interrogator")
# Tensor to PIL
def tensor2pil(image):
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
@@ -58,7 +104,53 @@ def image_analysis_fn(ci,image):
trending_ranks = {trending: sim for trending, sim in zip(top_trendings, ci.similarities(image_features, top_trendings))}
flavor_ranks = {flavor: sim for flavor, sim in zip(top_flavors, ci.similarities(image_features, top_flavors))}
return medium_ranks, artist_ranks, movement_ranks, trending_ranks, flavor_ranks
return {
"medium_ranks":medium_ranks,
"artist_ranks":artist_ranks,
"movement_ranks":movement_ranks,
"trending_ranks":trending_ranks,
"flavor_ranks":flavor_ranks
}
def generate_sentences(data):
sentences = []
# Get the length of data
data_length = len(data)
# Use a recursive function to handle variable-length data
def generate_recursive(index, current_sentence, current_score):
# Check if recursion is complete
if index == data_length:
sentences.append({"sentence": current_sentence, "score": current_score})
return
# Get the current level data
current_data = data[index]
# Iterate through the current level data
for phrase in current_data:
sentence = current_sentence + ("," if current_sentence.strip() else "") + phrase
score = current_score + current_data[phrase]
generate_recursive(index + 1, sentence, score)
# Start recursive generation of sentences
generate_recursive(0, "", 0)
# Sort the generated sentences by score in descending order
sentences.sort(key=lambda x: x["score"], reverse=True)
def get_random_elements(elements, num):
return random.sample(elements, num)
ps = get_random_elements(sentences, 5)
ps = [s["sentence"] for s in sorted(ps, key=lambda x: x["score"], reverse=True)]
return ps
def image_to_prompt(ci,image, mode):
ci.config.chunk_size = 2048 if ci.config.clip_model_name == "ViT-L-14/openai" else 1024
@@ -79,6 +171,10 @@ def image_to_prompt(ci,image, mode):
class ClipInterrogator:
global _available
available=_available
@classmethod
def INPUT_TYPES(s):
return {"required": {
@@ -86,17 +182,22 @@ class ClipInterrogator:
"prompt_mode": (['fast','classic','best','negative'],),
"image_analysis": (["off","on"],),
},
# "optional":{
# "output":("CLIPINTERROGATOR", {"multiline": True,"default": "", "dynamicPrompts": False})
# },
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("prompt",)
RETURN_TYPES = ("STRING","STRING",)
RETURN_NAMES = ("prompt","random_samples",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/prompt"
CATEGORY = "♾️Mixlab/Prompt"
OUTPUT_NODE = True
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
OUTPUT_IS_LIST = (True,True,)
global ci
ci = None
def run(self,image,prompt_mode,image_analysis):
@@ -156,5 +257,21 @@ class ClipInterrogator:
ci.caption_offloaded = True
# analysis_result=[]
# items = app.graph.getNodeById(31).widgets[2].value["items"]
random_samples=[]
return {"ui":{"prompt": prompt_result,"analysis":analysis_result},"result": (prompt_result,)}
for r in analysis_result:
random_sample = generate_sentences([r['medium_ranks'], r['artist_ranks'],r['movement_ranks'],r['trending_ranks'],r['flavor_ranks']])
for s in random_sample:
random_samples.append(s)
# print(len(random_samples))
# print('-----')
# print( random_samples)
return {
"ui":{
"prompt": prompt_result,
"analysis":analysis_result,
"random_samples":random_samples
},
"result": (prompt_result,random_samples,)}
+4 -4
View File
@@ -106,13 +106,13 @@ class CLIPSeg:
},
"optional":
{
"blur": ("FLOAT", {"min": 0, "max": 15, "step": 0.1, "default": 7}),
"threshold": ("FLOAT", {"min": 0, "max": 1, "step": 0.05, "default": 0.4}),
"blur": ("FLOAT", {"min": 0, "max": 15, "step": 0.1, "default": 3}),
"threshold": ("FLOAT", {"min": 0, "max": 1, "step": 0.05, "default": 0.3}),
"dilation_factor": ("INT", {"min": 0, "max": 10, "step": 1, "default": 4}),
}
}
CATEGORY = "♾️Mixlab/mask"
CATEGORY = "♾️Mixlab/Mask"
RETURN_TYPES = ("MASK", "IMAGE", "IMAGE",)
RETURN_NAMES = ("Mask","Heatmap Mask", "BW Mask")
@@ -218,7 +218,7 @@ class CombineMasks:
},
}
CATEGORY = "♾️Mixlab/mask"
CATEGORY = "♾️Mixlab/Mask"
RETURN_TYPES = ("MASK", "IMAGE", "IMAGE",)
RETURN_NAMES = ("Combined Mask","Heatmap Mask", "BW Mask")
+484 -169
View File
@@ -10,10 +10,12 @@ import folder_paths
import json,io
from comfy.cli_args import args
import cv2
import math
from .Watcher import FolderWatcher
FONT_PATH= os.path.abspath(os.path.join(os.path.dirname(__file__),'../assets/王汉宗颜楷体繁.ttf'))
MAX_RESOLUTION=8192
@@ -27,6 +29,94 @@ def pil2tensor(image):
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
# 颜色迁移
# Color-Transfer-between-Images https://github.com/chia56028/Color-Transfer-between-Images/blob/master/color_transfer.py
def get_mean_and_std(x):
x_mean, x_std = cv2.meanStdDev(x)
x_mean = np.hstack(np.around(x_mean,2))
x_std = np.hstack(np.around(x_std,2))
return x_mean, x_std
def color_transfer(source,target):
# sources = ['s1','s2','s3','s4','s5','s6']
# targets = ['t1','t2','t3','t4','t5','t6']
# 将PIL的Image类型转换为OpenCV的numpy数组
source = cv2.cvtColor(np.array(source), cv2.COLOR_RGB2LAB)
target = cv2.cvtColor(np.array(target), cv2.COLOR_RGB2LAB)
s_mean, s_std = get_mean_and_std(source)
t_mean, t_std = get_mean_and_std(target)
height, width, channel = source.shape
for i in range(0,height):
for j in range(0,width):
for k in range(0,channel):
x = source[i,j,k]
x = ((x-s_mean[k])*(t_std[k]/s_std[k]))+t_mean[k]
# round or +0.5
x = round(x)
# boundary check
x = 0 if x<0 else x
x = 255 if x>255 else x
source[i,j,k] = x
source = cv2.cvtColor(source,cv2.COLOR_LAB2RGB)
# 创建PIL图像对象
image_pil = Image.fromarray(source)
return image_pil
# 组合
def create_big_image(image_folder, image_count):
# 计算行数和列数
rows = math.ceil(math.sqrt(image_count))
cols = math.ceil(image_count / rows)
# 获取每个小图的尺寸
small_width = 100
small_height = 100
# 计算大图的尺寸
big_width = small_width * cols
big_height = small_height * rows
# 创建一个新的大图
big_image = Image.new('RGB', (big_width, big_height))
# 获取所有图片文件的路径
image_files = [f for f in os.listdir(image_folder) if os.path.isfile(os.path.join(image_folder, f))]
# 遍历所有图片文件
for i, image_file in enumerate(image_files):
# 打开图片并调整大小
image = Image.open(os.path.join(image_folder, image_file))
image = image.resize((small_width, small_height))
# 计算当前小图的位置
row = i // cols
col = i % cols
x = col * small_width
y = row * small_height
# 将小图粘贴到大图上
big_image.paste(image, (x, y))
return big_image
# # 调用方法并保存大图
# image_folder = 'path/to/folder/containing/images'
# image_count = 100
# big_image = create_big_image(image_folder, image_count)
# big_image.save('path/to/save/big_image.jpg')
def naive_cutout(img, mask,invert=True):
"""
Perform a simple cutout operation on an image using a mask.
@@ -203,6 +293,11 @@ def generate_gradient_image(width, height, start_color_hex, end_color_hex):
# gradient_image = generate_gradient_image(width, height, start_color_hex, end_color_hex)
# gradient_image.save('gradient_image.png')
def rgb_to_hex(rgb):
r, g, b = rgb
hex_color = "#{:02x}{:02x}{:02x}".format(r, g, b)
return hex_color
# 读取不了分层
def load_psd(image):
@@ -344,7 +439,8 @@ def get_average_color_image(image):
im = Image.new("RGB", (image.width, image.height), (average_red, average_green, average_blue))
return im
hex=rgb_to_hex((average_red, average_green, average_blue))
return (im,hex)
@@ -516,7 +612,7 @@ def merge_images(bg_image, layer_image, mask, x, y, width, height, scale_option)
return bg_image
# TODO 几个像素点的底
def resize_image(layer_image, scale_option, width, height,color="white"):
layer_image = layer_image.convert("RGB")
original_width, original_height = layer_image.size
@@ -540,11 +636,11 @@ def resize_image(layer_image, scale_option, width, height,color="white"):
elif scale_option == "center":
# Scale image to minimum of width and height, center it, and fill extra area with black
scale = min(width / original_width, height / original_height)
new_width = int(original_width * scale)
new_height = int(original_height * scale)
new_width = math.ceil(original_width * scale)
new_height = math.ceil(original_height * scale)
resized_image = Image.new("RGB", (width, height), color=color)
resized_image.paste(layer_image.resize((new_width, new_height)), ((width - new_width) // 2, (height - new_height) // 2))
resized_image=resized_image.convert("RGB")
resized_image = resized_image.convert("RGB")
return resized_image
return layer_image
@@ -552,6 +648,8 @@ def resize_image(layer_image, scale_option, width, height,color="white"):
# def generate_text_image(text_list, font_path, font_size, text_color, vertical=True, spacing=0):
# # Load Chinese font
# font = ImageFont.truetype(font_path, font_size)
@@ -591,8 +689,7 @@ def resize_image(layer_image, scale_option, width, height,color="white"):
# image=image.convert('RGB')
# return (image,alpha_image)
def generate_text_image(text, font_path, font_size, text_color, vertical=True, spacing=0):
def generate_text_image(text, font_path, font_size, text_color, vertical=True, stroke=False, stroke_color=(0, 0, 0), stroke_width=1, spacing=0):
# Split text into lines based on line breaks
lines = text.split("\n")
@@ -608,19 +705,17 @@ def generate_text_image(text, font_path, font_size, text_color, vertical=True, s
x = 0
y = 0
for i in range(len(lines)):
line=lines[i]
line = lines[i]
for char in line:
char_coordinates.append((x, y))
y += font_size + spacing
x += font_size + spacing
y = 0
# print(char_coordinates)
else:
x = 0
y = 0
for line in lines:
for char in line:
#print('char',char)
char_coordinates.append((x, y))
x += font_size + spacing
y += font_size + spacing
@@ -629,35 +724,42 @@ def generate_text_image(text, font_path, font_size, text_color, vertical=True, s
# 3. Calculate image width and height
if layout == "vertical":
width = (len(lines) * (font_size + spacing)) - spacing
height = ((len(max(lines, key=len))+1) * (font_size + spacing)) + spacing
height = ((len(max(lines, key=len)) + 1) * (font_size + spacing)) + spacing
else:
width = (len(max(lines, key=len)) * (font_size + spacing)) - spacing
height = ((len(lines)-1) * (font_size + spacing)) + font_size
height = ((len(lines) - 1) * (font_size + spacing)) + font_size
# 4. Draw each character on the image
image = Image.new('RGBA', (width, height), (255, 255, 255,0))
image = Image.new('RGBA', (width, height), (255, 255, 255, 0))
draw = ImageDraw.Draw(image)
font = ImageFont.truetype(font_path, font_size)
index=0
index = 0
for i, line in enumerate(lines):
for j, char in enumerate(line):
x, y = char_coordinates[index]
if stroke:
draw.text((x-stroke_width, y), char, font=font, fill=stroke_color)
draw.text((x+stroke_width, y), char, font=font, fill=stroke_color)
draw.text((x, y-stroke_width), char, font=font, fill=stroke_color)
draw.text((x, y+stroke_width), char, font=font, fill=stroke_color)
draw.text((x, y), char, font=font, fill=text_color)
index+=1
index += 1
# image.save(output_image_path)
# 分离alpha通道
# Separate alpha channel
alpha_channel = image.split()[3]
# 创建一个只有alpha通道的新图像
# Create a new image with only the alpha channel
alpha_image = Image.new('L', image.size)
alpha_image.putdata(alpha_channel.getdata())
image=image.convert('RGB')
image = image.convert('RGB')
return (image, alpha_image)
return (image,alpha_image)
def base64_to_image(base64_string):
# 去除前缀
@@ -720,7 +822,7 @@ class SmoothMask:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/mask"
CATEGORY = "♾️Mixlab/Mask"
INPUT_IS_LIST = False
@@ -745,79 +847,6 @@ class SmoothMask:
class FeatheredMask:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"mask": ("MASK",),
"start_offset":("INT", {"default": 1,
"min": -150,
"max": 150,
"step": 1,
"display": "slider"}),
"feathering_weight":("FLOAT", {"default": 0.1,
"min": 0.0,
"max": 1,
"step": 0.1,
"display": "slider"})
}
}
RETURN_TYPES = ('MASK',)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/mask"
OUTPUT_IS_LIST = (False,)
# 运行的函数
def run(self,mask,start_offset, feathering_weight):
# print(mask.shape,mask.size())
image=tensor2pil(mask)
# Open the image using PIL
image = image.convert("L")
if start_offset>0:
image=ImageOps.invert(image)
# Convert the image to a numpy array
image_np = np.array(image)
# Use Canny edge detection to get black contours
edges = cv2.Canny(image_np, 30, 150)
for i in range(0,abs(start_offset)):
# int(100*feathering_weight)
a=int(abs(start_offset)*0.1*i)
# Dilate the black contours to make them wider
kernel = np.ones((a, a), np.uint8)
dilated_edges = cv2.dilate(edges, kernel, iterations=1)
# dilated_edges = cv2.erode(edges, kernel, iterations=1)
# Smooth the dilated edges using Gaussian blur
smoothed_edges = cv2.GaussianBlur(dilated_edges, (5, 5), 0)
# Adjust the feathering weight
feathering_weight = max(0, min(feathering_weight, 1))
# Blend the smoothed edges with the original image to achieve feathering effect
image_np = cv2.addWeighted(image_np, 1, smoothed_edges, feathering_weight, feathering_weight)
# Convert the result back to PIL image
result_image = Image.fromarray(np.uint8(image_np))
result_image=result_image.convert("L")
if start_offset>0:
result_image=ImageOps.invert(result_image)
mask=pil2tensor(result_image)
# print(mask.shape,mask.size())
return mask
class SplitLongMask:
@@ -835,7 +864,7 @@ class SplitLongMask:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/mask"
CATEGORY = "♾️Mixlab/Mask"
OUTPUT_IS_LIST = (True,)
@@ -879,7 +908,7 @@ class TransparentImage:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/image"
CATEGORY = "♾️Mixlab/Image"
# INPUT_IS_LIST = True, 一个batch传进来
OUTPUT_IS_LIST = (True,True,True,)
@@ -887,7 +916,7 @@ class TransparentImage:
# 运行的函数
def run(self,images,masks,invert,save,filename_prefix,prompt=None, extra_pnginfo=None):
print('TransparentImage',images.shape,images.size())
# print('TransparentImage',images.shape,images.size(),masks.shape,masks.size())
# print(masks.shape,masks.size())
ui_images=[]
@@ -897,11 +926,16 @@ class TransparentImage:
masks_new=[]
nh=masks.shape[0]//count
#INPUT_IS_LIST = False, 一个batch传进来
if nh*count==masks.shape[0]:
masks_new=split_mask_by_new_height(masks,nh)
masks_new=masks
if images.shape[0]==masks.shape[0] and images.shape[1]==masks.shape[1] and images.shape[2]==masks.shape[2]:
print('TransparentImage',images.shape,images.size(),masks.shape,masks.size())
else:
masks_new=split_mask_by_new_height(masks,masks.shape[0])
#INPUT_IS_LIST = False, 一个batch传进来
if nh*count==masks.shape[0]:
masks_new=split_mask_by_new_height(masks,nh)
else:
masks_new=split_mask_by_new_height(masks,masks.shape[0])
is_save=True if save=='yes' else False
@@ -951,7 +985,7 @@ class EnhanceImage:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/image"
CATEGORY = "♾️Mixlab/Image"
INPUT_IS_LIST = True
@@ -1017,7 +1051,7 @@ class LoadImagesFromPath:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/image"
CATEGORY = "♾️Mixlab/Image"
# INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,True,False,)
@@ -1043,7 +1077,7 @@ class LoadImagesFromPath:
if watcher_folder!=None:
watcher_folder.stop()
#TODO 修bug: ps6477. tmp
images=get_images_filepath(file_path,white_bg=='enable')
# 当开启了监听,则取最新的,第一个文件
@@ -1071,7 +1105,7 @@ class LoadImagesFromPath:
print("发生了一个未知的错误:", str(e))
# print('#prompt::::',prompt)
return (imgs,masks,prompt,)
return {"ui": {"seed": [1]}, "result":(imgs,masks,prompt,)}
# TODO 扩大选区的功能,重新输出mask
@@ -1082,29 +1116,81 @@ class ImageCropByAlpha:
"RGBA": ("RGBA",), },
}
RETURN_TYPES = ("IMAGE",)
# RETURN_NAMES = ("WIDTH","HEIGHT","X","Y",)
RETURN_TYPES = ("IMAGE","MASK","MASK","INT","INT","INT","INT",)
RETURN_NAMES = ("IMAGE","MASK","AREA_MASK","x","y","width","height",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/image"
CATEGORY = "♾️Mixlab/Image"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,True,True,True,True,True,True,)
def run(self,image,RGBA):
# print(image.shape,RGBA.shape)
image=image[0]
RGBA=RGBA[0]
bf_im = tensor2pil(image)
# print(RGBA)
im=tensor2pil(RGBA)
im=naive_cutout(im,im)
x, y, w, h=get_not_transparent_area(im)
print('#ForImageCrop:',w, h,x, y,)
# print('#ForImageCrop:',w, h,x, y,)
x = min(x, image.shape[2] - 1)
y = min(y, image.shape[1] - 1)
to_x = w + x
to_y = h + y
x_1=x
y_1=y
width_1=w
height_1=h
img = image[:,y:to_y, x:to_x, :]
return (img,)
# 原图的mask
ori=RGBA[:,y:to_y, x:to_x, :]
ori=tensor2pil(ori)
# 创建一个新的图像对象,大小和模式与原始图像相同
new_image = Image.new("RGBA", ori.size)
# 获取原始图像的像素数据
pixel_data = ori.load()
# 获取新图像的像素数据
new_pixel_data = new_image.load()
# 遍历图像的每个像素
for y in range(ori.size[1]):
for x in range(ori.size[0]):
# 获取当前像素的RGBA值
r, g, b, a = pixel_data[x, y]
# 如果a通道不为0(不透明),将当前像素设置为白色
if a != 0:
new_pixel_data[x, y] = (255, 255, 255, 255)
else:
new_pixel_data[x, y] = (r, g, b, a)
# 保存修改后的图像
# new_image.save("output.png")
ori=new_image.convert('L')
# threshold = 128
# ori = ori.point(lambda x: 0 if x < threshold else 255, '1')
ori=pil2tensor(ori)
# 矩形区域,mask
b_image =AreaToMask_run(RGBA)
# img=None
# b_image=None
return ([img],[ori],[b_image],[x_1],[y_1],[width_1],[height_1],)
@@ -1133,6 +1219,7 @@ class TextImage:
}),
"text_color":("STRING",{"multiline": False,"default": "#000000","dynamicPrompts": False}),
"vertical":("BOOLEAN", {"default": True},),
"stroke":("BOOLEAN", {"default": False},),
},
}
@@ -1141,16 +1228,16 @@ class TextImage:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/image"
CATEGORY = "♾️Mixlab/Image"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,False,)
def run(self,text,font_path,font_size,spacing,text_color,vertical):
def run(self,text,font_path,font_size,spacing,text_color,vertical,stroke):
# text_list=list(text)
img,mask=generate_text_image(text,font_path,font_size,text_color,vertical,spacing)
# stroke=False, stroke_color=(0, 0, 0), stroke_width=1, spacing=0
img,mask=generate_text_image(text,font_path,font_size,text_color,vertical,stroke,(0, 0, 0),1,spacing)
img=pil2tensor(img)
mask=pil2tensor(mask)
@@ -1170,7 +1257,7 @@ class LoadImagesFromURL:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/image"
CATEGORY = "♾️Mixlab/Image"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (True,True,)
@@ -1227,7 +1314,7 @@ class SvgImage:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/image"
CATEGORY = "♾️Mixlab/Image"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,True,)
@@ -1261,7 +1348,7 @@ class Image3D:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/image"
CATEGORY = "♾️Mixlab/Image"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,False,False,False,)
@@ -1301,6 +1388,24 @@ class Image3D:
def AreaToMask_run(RGBA):
# print(RGBA)
im=tensor2pil(RGBA)
im=naive_cutout(im,im)
x, y, w, h=get_not_transparent_area(im)
im=im.convert("RGBA")
# print('#AreaToMask:',im)
img=areaToMask(x,y,w,h,im)
img=img.convert("RGBA")
mask=pil2tensor(img)
channels = ["red", "green", "blue", "alpha"]
# print(mask,mask.shape)
mask = mask[:, :, :, channels.index("green")]
return mask
class AreaToMask:
@classmethod
@@ -1313,26 +1418,14 @@ class AreaToMask:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/mask"
CATEGORY = "♾️Mixlab/Mask"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
def run(self,RGBA):
# print(RGBA)
im=tensor2pil(RGBA)
im=naive_cutout(im,im)
x, y, w, h=get_not_transparent_area(im)
im=im.convert("RGBA")
# print('#AreaToMask:',im)
img=areaToMask(x,y,w,h,im)
img=img.convert("RGBA")
mask=pil2tensor(img)
channels = ["red", "green", "blue", "alpha"]
# print(mask,mask.shape)
mask = mask[:, :, :, channels.index("green")]
mask =AreaToMask_run(RGBA)
return (mask,)
@@ -1348,7 +1441,7 @@ class FaceToMask:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/mask"
CATEGORY = "♾️Mixlab/Mask"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
@@ -1393,7 +1486,7 @@ class EmptyLayer:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/layer"
CATEGORY = "♾️Mixlab/Layer"
OUTPUT_IS_LIST = (True,)
@@ -1474,7 +1567,7 @@ class NewLayer:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/layer"
CATEGORY = "♾️Mixlab/Layer"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
@@ -1505,6 +1598,112 @@ class NewLayer:
return (layer_n,)
def splitImage(image, num):
width, height = image.size
num_rows = int(num ** 0.5)
num_cols = int(num / num_rows)
grid_width = width // num_cols
grid_height = height // num_rows
grid_coordinates = []
for i in range(num_rows):
for j in range(num_cols):
x = j * grid_width
y = i * grid_height
grid_coordinates.append((x, y, grid_width, grid_height))
return grid_coordinates
# # 读取图片
# image = Image.open("path_to_your_image.jpg")
# # 定义要切割的区域数量
# num = 9
# # 切割图片
# grid_coordinates = splitImage(image, num)
# # 输出切割区域坐标
# for i, coordinates in enumerate(grid_coordinates):
# print(f"Region {i + 1}: x={coordinates[0]}, y={coordinates[1]}, width={coordinates[2]}, height={coordinates[3]}")
class SplitImage:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"image": ("IMAGE",),
"num": ("INT",{
"default": 4,
"min": 1, #Minimum value
"max": 500, #Maximum value
"step": 1, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
}),
"seed": ("INT",{
"default": 4,
"min": 1, #Minimum value
"max": 500, #Maximum value
"step": 1, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
}),
}
}
RETURN_TYPES = ("_GRID","_GRID",)
RETURN_NAMES = ("grids","grid")
FUNCTION = "run"
CATEGORY = "♾️Mixlab/Layer"
INPUT_IS_LIST = False
# OUTPUT_IS_LIST = (True,)
def run(self,image,num,seed):
image=tensor2pil(image)
grids=splitImage(image,num)
if seed>=num:
num=int(seed / 500 * num)-1
else:
num=seed-1
num=max(0,num)
g=grids[num]
return (grids,g,)
class GridOutput:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"grid": ("_GRID",)
}
}
RETURN_TYPES = ("INT","INT","INT","INT",)
RETURN_NAMES = ("x","y","width","height",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/Layer"
INPUT_IS_LIST = False
# OUTPUT_IS_LIST = (True,)
def run(self,grid):
x,y,w,h=grid
return (x,y,w,h,)
class ShowLayer:
@classmethod
def INPUT_TYPES(s):
@@ -1562,7 +1761,7 @@ class ShowLayer:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/layer"
CATEGORY = "♾️Mixlab/Layer"
INPUT_IS_LIST = True
# OUTPUT_IS_LIST = (True,)
@@ -1601,12 +1800,12 @@ class MergeLayers:
}
RETURN_TYPES = ("IMAGE",)
# RETURN_NAMES = ("WIDTH","HEIGHT","X","Y",)
RETURN_TYPES = ("IMAGE","MASK",)
RETURN_NAMES = ("IMAGE","MASK",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/layer"
CATEGORY = "♾️Mixlab/Layer"
INPUT_IS_LIST = True
# OUTPUT_IS_LIST = (False,)
@@ -1627,7 +1826,10 @@ class MergeLayers:
bg_image=tensor2pil(bg_image)
# 按z-index排序
layers_new = sorted(layers, key=lambda x: x["z_index"])
width, height = bg_image.size
final_mask= Image.new('L', (width, height), 0)
for layer in layers_new:
image=layer['image']
mask=layer['mask']
@@ -1653,25 +1855,32 @@ class MergeLayers:
layer['scale_option']
)
mask=bg_image.convert('RGBA')
mask=pil2tensor(mask)
final_mask=merge_images(final_mask,
layer_mask.convert('RGB'),
layer_mask,
layer['x'],
layer['y'],
layer['width'],
layer['height'],
layer['scale_option']
)
final_mask=final_mask.convert('L')
# mask=bg_image.convert('RGBA')
final_mask=pil2tensor(final_mask)
bg_image=bg_image.convert('RGB')
bg_image=pil2tensor(bg_image)
channels = ["red", "green", "blue", "alpha"]
# print(mask,mask.shape)
mask = mask[:, :, :, channels.index("green")]
bg_images.append(bg_image)
masks.append(mask)
masks.append(final_mask)
bg_images=torch.cat(bg_images, dim=0)
masks=torch.cat(masks, dim=0)
return (bg_images,masks,)
class GradientImage:
@classmethod
def INPUT_TYPES(s):
@@ -1702,7 +1911,7 @@ class GradientImage:
FUNCTION = "run"
# 右键菜单目录
CATEGORY = "♾️Mixlab/image"
CATEGORY = "♾️Mixlab/Image"
# 输入是否为列表
INPUT_IS_LIST = False
@@ -1782,7 +1991,7 @@ class NoiseImage:
FUNCTION = "run"
# 右键菜单目录
CATEGORY = "♾️Mixlab/image"
CATEGORY = "♾️Mixlab/Image"
# 输入是否为列表
INPUT_IS_LIST = False
@@ -1852,15 +2061,15 @@ class ResizeImage:
}
}
RETURN_TYPES = ("IMAGE","IMAGE")
RETURN_NAMES = ("image","average_image",)
RETURN_TYPES = ("IMAGE","IMAGE","STRING",)
RETURN_NAMES = ("image","average_image","average_hex",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/image"
CATEGORY = "♾️Mixlab/Image"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,True,)
OUTPUT_IS_LIST = (True,True,True,)
def run(self,width,height,scale_option,image=None,average_color=['on'],fill_color=["#FFFFFF"]):
@@ -1872,28 +2081,134 @@ class ResizeImage:
imgs=[]
average_images=[]
hexs=[]
if image==None:
im=create_noisy_image(w,h,"RGB")
a_im=get_average_color_image(im)
a_im,hex=get_average_color_image(im)
im=pil2tensor(im)
imgs.append(im)
a_im=pil2tensor(a_im)
average_images.append(a_im)
hexs.append(hex)
else:
for im in image:
im=tensor2pil(im)
im=resize_image(im,scale_option,w,h,fill_color)
im=im.convert('RGB')
for ims in image:
for im in ims:
im=tensor2pil(im)
im=resize_image(im,scale_option,w,h,fill_color)
im=im.convert('RGB')
a_im=get_average_color_image(im)
a_im,hex=get_average_color_image(im)
im=pil2tensor(im)
imgs.append(im)
im=pil2tensor(im)
imgs.append(im)
a_im=pil2tensor(a_im)
average_images.append(a_im)
a_im=pil2tensor(a_im)
average_images.append(a_im)
hexs.append(hex)
return (imgs,average_images,)
return (imgs,average_images,hexs,)
class MirroredImage:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"image": ("IMAGE",),
},
}
# 输出的数据类型
RETURN_TYPES = ("IMAGE",)
# 运行时方法名称
FUNCTION = "run"
# 右键菜单目录
CATEGORY = "♾️Mixlab/Image"
# 输入是否为列表
INPUT_IS_LIST = True
# 输出是否为列表
OUTPUT_IS_LIST = (True,)
def run(self,image):
res=[]
for ims in image:
for im in ims:
img=tensor2pil(im)
mirrored_image = img.transpose(Image.FLIP_LEFT_RIGHT)
img=pil2tensor(mirrored_image)
res.append(img)
return (res,)
class GetImageSize_:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
}
}
RETURN_TYPES = ("INT", "INT")
RETURN_NAMES = ("width", "height")
FUNCTION = "get_size"
CATEGORY = "♾️Mixlab/Image"
def get_size(self, image):
_, height, width, _ = image.shape
return (width, height)
class ImageColorTransfer:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"source": ("IMAGE",),
"target": ("IMAGE",),
},
}
# 输出的数据类型
RETURN_TYPES = ("IMAGE",)
# 运行时方法名称
FUNCTION = "run"
# 右键菜单目录
CATEGORY = "♾️Mixlab/Image"
# 输入是否为列表
INPUT_IS_LIST = True
# 输出是否为列表
OUTPUT_IS_LIST = (True,)
def run(self,source,target):
res=[]
target=target[0][0]
print(target.shape)
target=tensor2pil(target)
for ims in source:
for im in ims:
image=tensor2pil(im)
image=color_transfer(image,target)
image=pil2tensor(image)
res.append(image)
return (res,)
+40 -4
View File
@@ -1,12 +1,46 @@
import os
import os,sys
import folder_paths
from simple_lama_inpainting import SimpleLama
from PIL import Image
from PIL import Image
import importlib.util
import numpy as np
import torch
global _available
_available=False
def is_installed(package):
try:
spec = importlib.util.find_spec(package)
except ModuleNotFoundError:
return False
return spec is not None
if is_installed('simple_lama_inpainting')==False:
import subprocess
from packaging import version
if version.parse(torch.__version__)>=version.parse('2.1'):
# 安装
print('#pip install simple_lama_inpainting')
result = subprocess.run([sys.executable, '-s', '-m', 'pip', 'install', 'simple_lama_inpainting'], capture_output=True, text=True)
#检查命令执行结果
if result.returncode == 0:
print("#install success")
from simple_lama_inpainting import SimpleLama
_available=True
else:
print("#install error")
else:
print('#pls check your torch version >= 2.1')
else:
from simple_lama_inpainting import SimpleLama
_available=True
llma_model_path=os.path.join(folder_paths.models_dir, "lama/big-lama.pt")
@@ -38,6 +72,8 @@ def pil2tensor(image):
class LaMaInpainting:
global _available
available=_available
@classmethod
def INPUT_TYPES(s):
return {"required": {
@@ -52,7 +88,7 @@ class LaMaInpainting:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/image"
CATEGORY = "♾️Mixlab/Image"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
+171
View File
@@ -0,0 +1,171 @@
import scipy.ndimage
import torch
from nodes import MAX_RESOLUTION
import numpy as np
# from PIL import Image, ImageDraw
from PIL import Image, ImageOps
from comfy.cli_args import args
import cv2
# Tensor to PIL
def tensor2pil(image):
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
# Convert PIL to Tensor
def pil2tensor(image):
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
def grow(mask, expand, tapered_corners):
c = 0 if tapered_corners else 1
kernel = np.array([[c, 1, c],
[1, 1, 1],
[c, 1, c]])
mask = mask.reshape((-1, mask.shape[-2], mask.shape[-1]))
out = []
for m in mask:
output = m.numpy()
for _ in range(abs(expand)):
if expand < 0:
output = scipy.ndimage.grey_erosion(output, footprint=kernel)
else:
output = scipy.ndimage.grey_dilation(output, footprint=kernel)
output = torch.from_numpy(output)
out.append(output)
return torch.stack(out, dim=0)
def combine(destination, source, x, y):
output = destination.reshape((-1, destination.shape[-2], destination.shape[-1])).clone()
source = source.reshape((-1, source.shape[-2], source.shape[-1]))
left, top = (x, y,)
right, bottom = (min(left + source.shape[-1], destination.shape[-1]), min(top + source.shape[-2], destination.shape[-2]))
visible_width, visible_height = (right - left, bottom - top,)
source_portion = source[:, :visible_height, :visible_width]
destination_portion = destination[:, top:bottom, left:right]
#operation == "subtract":
output[:, top:bottom, left:right] = destination_portion - source_portion
output = torch.clamp(output, 0.0, 1.0)
return output
class OutlineMask:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"mask": ("MASK",),
"outline_width":("INT", {"default": 10,"min": 1, "max": MAX_RESOLUTION, "step": 1}),
"tapered_corners": ("BOOLEAN", {"default": True}),
}
}
RETURN_TYPES = ('MASK',)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/Mask"
# 运行的函数
def run(self, mask, outline_width, tapered_corners):
m1=grow(mask,outline_width,tapered_corners)
m2=grow(mask,-outline_width,tapered_corners)
m3=combine(m1,m2,0,0)
return (m3,)
class FeatheredMask:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"mask": ("MASK",),
"start_offset":("INT", {"default": 1,
"min": -150,
"max": 150,
"step": 1,
"display": "slider"}),
"feathering_weight":("FLOAT", {"default": 0.1,
"min": 0.0,
"max": 1,
"step": 0.1,
"display": "slider"})
}
}
RETURN_TYPES = ('MASK',)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/Mask"
OUTPUT_IS_LIST = (True,)
# 运行的函数
def run(self,mask,start_offset, feathering_weight):
# print(mask.shape,mask.size())
num,_,_=mask.size()
masks=[]
for i in range(num):
mm=mask[i]
image=tensor2pil(mm)
# Open the image using PIL
image = image.convert("L")
if start_offset>0:
image=ImageOps.invert(image)
# Convert the image to a numpy array
image_np = np.array(image)
# Use Canny edge detection to get black contours
edges = cv2.Canny(image_np, 30, 150)
for i in range(0,abs(start_offset)):
# int(100*feathering_weight)
a=int(abs(start_offset)*0.1*i)
# Dilate the black contours to make them wider
kernel = np.ones((a, a), np.uint8)
dilated_edges = cv2.dilate(edges, kernel, iterations=1)
# dilated_edges = cv2.erode(edges, kernel, iterations=1)
# Smooth the dilated edges using Gaussian blur
smoothed_edges = cv2.GaussianBlur(dilated_edges, (5, 5), 0)
# Adjust the feathering weight
feathering_weight = max(0, min(feathering_weight, 1))
# Blend the smoothed edges with the original image to achieve feathering effect
image_np = cv2.addWeighted(image_np, 1, smoothed_edges, feathering_weight, feathering_weight)
# Convert the result back to PIL image
result_image = Image.fromarray(np.uint8(image_np))
result_image=result_image.convert("L")
if start_offset>0:
result_image=ImageOps.invert(result_image)
result_image=result_image.convert("L")
mt=pil2tensor(result_image)
masks.append(mt)
# print( mt.size())
return (masks,)
+120 -80
View File
@@ -12,6 +12,30 @@ from PIL.PngImagePlugin import PngInfo
# req = request.Request("http://127.0.0.1:8188/prompt", data=data)
# request.urlopen(req)
embeddings_path=os.path.join(folder_paths.models_dir, "embeddings")
def get_files_with_extension(directory, extension):
file_list = []
for root, dirs, files in os.walk(directory):
for file in files:
if file.endswith(extension):
file_name = os.path.splitext(file)[0]
file_list.append(file_name)
return file_list
def join_with_(text_list,delimiter):
joined_text = delimiter.join(text_list)
return joined_text
class AnyType(str):
"""A special class that is always equal in not equal comparisons. Credit to pythongosssss"""
def __ne__(self, __value: object) -> bool:
return False
any_type = AnyType("*")
default_prompt1='''Swing
Slide
@@ -54,7 +78,7 @@ def addWeight(text, weight=1):
if weight == 1:
return text
else:
return f"({text}:{round(weight,2)})"
return f"({text}:{round(weight,3)})"
def prompt_delete_words(sentence, new_words_length):
# 使用逗号分割句子,并去除空格
@@ -85,8 +109,8 @@ def prompt_delete_words(sentence, new_words_length):
class PromptImage:
def __init__(self):
self.temp_dir = folder_paths.get_temp_directory()
self.type = "temp"
self.output_dir = folder_paths.get_output_directory()
self.type = "output"
self.prefix_append = "PromptImage"
self.compress_level = 4
@@ -114,38 +138,43 @@ class PromptImage:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/prompt"
CATEGORY = "♾️Mixlab/Prompt"
# 运行的函数
def run(self,prompts,images,save_to_image):
filename_prefix="mixlab_"
filename_prefix += self.prefix_append
full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(
filename_prefix, self.temp_dir, images[0].shape[1], images[0].shape[0])
filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0])
results = list()
save_to_image=save_to_image[0]=='enable'
for index in range(len(images)):
image=images[index]
img=tensor2pil(image)
metadata = None
if save_to_image:
metadata = PngInfo()
prompt_text=prompts[index]
if prompt_text is not None:
metadata.add_text("prompt_text", prompt_text)
file = f"{filename}_{index}_{counter:05}_.png"
img.save(os.path.join(full_output_folder, file), pnginfo=metadata, compress_level=self.compress_level)
results.append({
"filename": file,
"subfolder": subfolder,
"type": self.type
})
counter += 1
res=[]
imgs=images[index]
for image in imgs:
img=tensor2pil(image)
metadata = None
if save_to_image:
metadata = PngInfo()
prompt_text=prompts[index]
if prompt_text is not None:
metadata.add_text("prompt_text", prompt_text)
file = f"{filename}_{index}_{counter:05}_.png"
img.save(os.path.join(full_output_folder, file), pnginfo=metadata, compress_level=self.compress_level)
res.append({
"filename": file,
"subfolder": subfolder,
"type": self.type
})
counter += 1
results.append(res)
return { "ui": { "_images": results,"prompts":prompts } }
@@ -188,7 +217,7 @@ class PromptSimplification:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/prompt"
CATEGORY = "♾️Mixlab/Prompt"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
@@ -203,6 +232,8 @@ class PromptSimplification:
for n in nps:
result.append(n)
result= [elem.strip() for elem in result if elem.strip()]
return {"ui": {"prompts": result}, "result": (result,)}
@@ -247,7 +278,7 @@ class PromptSlide:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/prompt"
CATEGORY = "♾️Mixlab/Prompt"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
@@ -290,7 +321,9 @@ class RandomPrompt:
"default": 'sticker, Cartoon, ``'
}),
"random_sample": (["enable", "disable"],),
}
# "seed": ("INT", {"default": 0, "min": 0, "max": 0xffffffffffffffff, "step": 1}),
},
}
@@ -299,7 +332,7 @@ class RandomPrompt:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/prompt"
CATEGORY = "♾️Mixlab/Prompt"
OUTPUT_IS_LIST = (True,)
OUTPUT_NODE = True
@@ -327,7 +360,10 @@ class RandomPrompt:
for w2 in words2:
w2=w2.strip()
if '``' not in w2:
w2=w2+',``'
if w2=="":
w2='``'
else:
w2=w2+',``'
if w1!='' and w2!='':
prompts.append(w2.replace('``', w1))
pbar.update(1)
@@ -341,6 +377,8 @@ class RandomPrompt:
else:
prompts = prompts[:min(max_count,len(prompts))]
prompts= [elem.strip() for elem in prompts if elem.strip()]
# return (new_prompt)
return {"ui": {"prompts": prompts}, "result": (prompts,)}
@@ -348,62 +386,64 @@ class RandomPrompt:
# class RunWorkflow:
# @classmethod
# def INPUT_TYPES(s):
# return {
# "required": {
# "workflow": ("STRING", {
# "multiline": False,
# "default": ''
# }),
# "prompt": ("STRING", {
# "multiline": False,
# "default": ''
# }),
# "image": ("IMAGE",),
# "input_node": ("STRING", {
# "multiline": False,
# "default": ''
# }),
# "output_node": ("STRING", {
# "multiline": False,
# "default": ''
# }),
# },
class EmbeddingPrompt:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"embedding":(get_files_with_extension(embeddings_path,'.pt'),),
"weight": ("FLOAT", {"default": 1, "min": -2, "max": 2,"step":0.01 ,"display": "slider"}),
},
# }
}
RETURN_TYPES = ("STRING",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/Prompt"
OUTPUT_IS_LIST = (False,)
# OUTPUT_NODE = True
# 运行的函数
def run(self,embedding,weight):
weight = round(weight, 3)
prompt='embedding:'+embedding
if weight!=1:
prompt='('+prompt+':'+str(weight)+')'
prompt=" "+prompt+' '
# return (new_prompt)
return (prompt,)
RETURN_TYPES = (any_type,)
class JoinWithDelimiter:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"text_list": (any_type,),
"delimiter":(["newline","comma"],),
},
}
RETURN_TYPES = ("STRING",)
# RETURN_TYPES = ("IMAGE","STRING",)
FUNCTION = "run"
# FUNCTION = "run"
CATEGORY = "♾️Mixlab/Prompt"
# CATEGORY = "♾️Mixlab/workflow"
# OUTPUT_IS_LIST = (True,)
# OUTPUT_NODE = True
# # 运行的函数
# def run(self,workflow,prompt,image,input_node,output_node):
# print('#运行的函数',prompt,image,input_node,output_node)
# workflow=json.loads(workflow)
# input_node=input_node.split(".")
# workflow[input_node[0]][input_node[1]][input_node[2]]=prompt
# workflow_new={}
# # 遍历,seed设为随机
# for key, value in workflow.items():
# if 'inputs' in value:
# if 'seed' in value['inputs']:
# value['inputs']['seed']= random.randint(1, 18446744073709551614)
# workflow_new[key]=value
# queue_prompt(workflow_new)
# print('#运行的函数',workflow_new[input_node[0]])
# # return (new_prompt)
# return {"ui":{"images": []},"result": ([image],['text'],)}
INPUT_IS_LIST = True # 当true的时候,输入时list,当false的时候,如果输入是list,则会自动包一层for循环调用
OUTPUT_IS_LIST = (False,)
def run(self,text_list,delimiter):
delimiter=delimiter[0]
if delimiter =='newline':
delimiter='\n'
elif delimiter=='comma':
delimiter=','
t=''
if isinstance(text_list, list):
t=join_with_(text_list,delimiter)
return (t,)
+164
View File
@@ -0,0 +1,164 @@
import os,sys
import folder_paths
from PIL import Image
import importlib.util
import comfy.utils
import numpy as np
import torch
U2NET_HOME=os.path.join(folder_paths.models_dir, "rembg")
os.environ["U2NET_HOME"] = U2NET_HOME
global _available
_available=False
def is_installed(package):
try:
spec = importlib.util.find_spec(package)
except ModuleNotFoundError:
return False
return spec is not None
try:
if is_installed('rembg')==False:
import subprocess
# 安装
print('#pip install rembg[gpu]')
result = subprocess.run([sys.executable, '-s', '-m', 'pip', 'install', 'rembg[gpu]'], capture_output=True, text=True)
#检查命令执行结果
if result.returncode == 0:
print("#install success")
from rembg import new_session, remove
_available=True
else:
print("#install error")
else:
from rembg import new_session, remove
_available=True
except:
_available=False
def run_bg(model_name= "unet",images=[]):
# model_name = "unet" # "isnet-general-use"
rembg_session = new_session(model_name)
masks=[]
rgba_images=[]
rgb_images=[]
# 进度条
pbar = comfy.utils.ProgressBar(len(images) )
for img in images:
# use the post_process_mask argument to post process the mask to get better results.
mask = remove(img, session=rembg_session,only_mask=True,post_process_mask=True)
# mask=mask.convert('L')
# masks.append(mask)
if model_name=="u2net_cloth_seg":
width, original_height = mask.size
num_slices = original_height // img.height
for i in range(num_slices):
top = i * img.height
bottom = (i + 1) * img.height
slice_image = mask.crop((0, top, width, bottom))
slice_mask=slice_image.convert('L')
masks.append(slice_mask)
# rgba图
image_rgba = img.convert("RGBA")
image_rgba.putalpha(slice_mask)
rgba_images.append(image_rgba)
#rgb
rgb_image = Image.new("RGB", image_rgba.size, (0, 0, 0))
rgb_image.paste(image_rgba, mask=image_rgba.split()[3])
rgb_images.append(rgb_image)
else:
mask=mask.convert('L')
# mask.save(output_path)
masks.append(mask)
# rgba图
image_rgba = img.convert("RGBA")
image_rgba.putalpha(mask)
rgba_images.append(image_rgba)
#rgb
rgb_image = Image.new("RGB", image_rgba.size, (0, 0, 0))
rgb_image.paste(image_rgba, mask=image_rgba.split()[3])
rgb_images.append(rgb_image)
pbar.update(1)
return (masks,rgba_images,rgb_images)
# Tensor to PIL
def tensor2pil(image):
return Image.fromarray(np.clip(255. * image.cpu().numpy().squeeze(), 0, 255).astype(np.uint8))
# Convert PIL to Tensor
def pil2tensor(image):
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
class RembgNode_:
global _available
available=_available
@classmethod
def INPUT_TYPES(s):
return {"required": {
"image": ("IMAGE",),
"model_name": (["u2net",
"u2netp",
"u2net_human_seg",
"u2net_cloth_seg",
"silueta",
"isnet-general-use",
"isnet-anime",
# "sam"
],),
},
}
RETURN_TYPES = ("MASK","IMAGE","RGBA",)
RETURN_NAMES = ("masks","images","RGBAs")
FUNCTION = "run"
CATEGORY = "♾️Mixlab/Mask"
OUTPUT_NODE = True
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,True,True,)
def run(self,image,model_name):
# 兼容list输入和batch输入
model_name=model_name[0]
images=[]
for ims in image:
for im in ims:
im=tensor2pil(im)
images.append(im)
masks,rgba_images,rgb_images=run_bg(model_name,images)
masks=[pil2tensor(m) for m in masks]
rgba_images=[pil2tensor(m) for m in rgba_images]
rgb_images=[pil2tensor(m) for m in rgb_images]
return (masks,rgb_images,rgba_images,)
+2 -2
View File
@@ -93,7 +93,7 @@ class ScreenShareNode:
RETURN_NAMES = ("IMAGE","PROMPT","FLOAT","INT")
FUNCTION = "run"
CATEGORY = "♾️Mixlab/image"
CATEGORY = "♾️Mixlab/Image"
# INPUT_IS_LIST = True
OUTPUT_IS_LIST = (False,False,False,False)
@@ -118,7 +118,7 @@ class FloatingVideo:
OUTPUT_NODE = True
FUNCTION = "run"
CATEGORY = "♾️Mixlab/image"
CATEGORY = "♾️Mixlab/Image"
# INPUT_IS_LIST = True
# OUTPUT_IS_LIST = (False,False,)
+307
View File
@@ -0,0 +1,307 @@
from transformers import pipeline, set_seed,AutoTokenizer, AutoModelForSeq2SeqLM
import random
import re
import os,sys
import folder_paths
# from PIL import Image
import importlib.util
import comfy.utils
# import numpy as np
import torch
import random
global _available
_available=True
text_generator_model_path=os.path.join(folder_paths.models_dir, "prompt_generator/text2image-prompt-generator")
if not os.path.exists(text_generator_model_path):
print(f"## text_generator_model not found: {text_generator_model_path}, pls download from https://huggingface.co/succinctly/text2image-prompt-generator/tree/main")
text_generator_model_path='succinctly/text2image-prompt-generator'
zh_en_model_path=os.path.join(folder_paths.models_dir, "prompt_generator/opus-mt-zh-en")
if not os.path.exists(zh_en_model_path):
print(f"## zh_en_model not found: {zh_en_model_path}, pls download from https://huggingface.co/Helsinki-NLP/opus-mt-zh-en/tree/main")
zh_en_model_path='Helsinki-NLP/opus-mt-zh-en'
def is_installed(package):
try:
spec = importlib.util.find_spec(package)
except ModuleNotFoundError:
return False
return spec is not None
try:
if is_installed('sentencepiece')==False:
import subprocess
# 安装
print('#pip install sentencepiece')
result = subprocess.run([sys.executable, '-s', '-m', 'pip', 'install', 'sentencepiece'], capture_output=True, text=True)
#检查命令执行结果
if result.returncode == 0 and is_installed('sentencepiece'):
print("#install success")
_available=True
else:
print("#install error")
_available=False
else:
_available=True
except:
_available=False
def translate(zh_en_tokenizer,zh_en_model,text):
with torch.no_grad():
encoded = zh_en_tokenizer([text], return_tensors="pt")
encoded.to(zh_en_model.device)
sequences = zh_en_model.generate(**encoded)
return zh_en_tokenizer.batch_decode(sequences, skip_special_tokens=True)[0]
# input = "青春不能回头,所以青春没有终点。 ——《火影忍者》"
# print(input, translate(input))
def text_generate(text_pipe,input,seed=None):
if seed==None:
seed = random.randint(100, 1000000)
set_seed(seed)
for count in range(6):
sequences = text_pipe(input, max_length=random.randint(60, 90), num_return_sequences=8)
list = []
for sequence in sequences:
line = sequence['generated_text'].strip()
if line != input and len(line) > (len(input) + 4) and line.endswith((":", "-", "—")) is False:
list.append(line)
result = "\n".join(list)
result = re.sub('[^ ]+\.[^ ]+','', result)
result = result.replace("<", "").replace(">", "")
if result != "":
return result
if count == 5:
return result
# input = "Youth can't turn back, so there's no end to youth."
# print(input, text_generate(input))
import re
def correct_prompt_syntax(prompt):
print("input prompt",prompt)
corrected_elements = []
# 处理成统一的英文标点
prompt = prompt.replace('(', '(').replace(')', ')').replace(',', ',').replace(';', ',').replace('。', '.').replace(':',':')
# 删除多余的空格
prompt = re.sub(r'\s+', ' ', prompt).strip()
# 分词
prompt_elements = prompt.split(',')
for element in prompt_elements:
element = element.strip()
# 处理空元素
if not element:
continue
# 检查并处理圆括号、方括号、尖括号
if element[0] in '([':
corrected_element = balance_brackets(element, '(', ')') if element[0] == '(' else balance_brackets(element, '[', ']')
elif element[0] == '<':
corrected_element = balance_brackets(element, '<', '>')
else:
# 删除开头的右括号或右方括号
corrected_element = element.lstrip(')]')
corrected_elements.append(corrected_element)
# 重组修正后的prompt
corrected_prompt = ', '.join(corrected_elements)
print("output prompt",corrected_prompt)
return corrected_prompt
def balance_brackets(element, open_bracket, close_bracket):
open_brackets_count = element.count(open_bracket)
close_brackets_count = element.count(close_bracket)
return element + close_bracket * (open_brackets_count - close_brackets_count)
# # 示例使用
# test_prompt = "((middle-century castles)), [forsaken: 0.8], (mystery dragons: 1.3, mist forests, sunsets, quiet; (((dummy)), [fisting city: 0.5] background, radiant, soft and flavoured,] promising mountains, ((starry: 1.6), [[crowds], [middle-century castle: urban landscapes of the future: 0.5], [yellow: bright sun: 0.7], overlooking"
# corrected_prompt = correct_prompt_syntax(test_prompt)
# print(corrected_prompt)
class ChinesePrompt:
global _available
available=_available
@classmethod
def INPUT_TYPES(s):
return {"required": {
"text": ("STRING",{"multiline": True,"default": "", "dynamicPrompts": False}),
"generation": (["on","off"],{"default": "off"}),
},
"optional":{
"seed":("INT", {"default": 100, "min": 100, "max": 1000000}),
},
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("prompt",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/Prompt"
OUTPUT_NODE = True
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
global text_pipe,zh_en_model,zh_en_tokenizer
text_pipe= None
zh_en_model=None
zh_en_tokenizer=None
def run(self,text,seed,generation):
global text_pipe,zh_en_model,zh_en_tokenizer
seed=seed[0]
generation=generation[0]
# 进度条
pbar = comfy.utils.ProgressBar(len(text)+1)
texts = [correct_prompt_syntax(t) for t in text]
print('correct_prompt_syntax::',texts)
if zh_en_model==None:
zh_en_model = AutoModelForSeq2SeqLM.from_pretrained(zh_en_model_path).eval()
zh_en_tokenizer = AutoTokenizer.from_pretrained(zh_en_model_path,padding=True, truncation=True)
zh_en_model.to("cuda" if torch.cuda.is_available() else "cpu")
# zh_en_tokenizer.to("cuda" if torch.cuda.is_available() else "cpu")
text_pipe=pipeline('text-generation', model=text_generator_model_path,device="cuda" if torch.cuda.is_available() else "cpu")
# text_pipe.model.to("cuda" if torch.cuda.is_available() else "cpu")
prompt_result=[]
# print('zh_en_model device',zh_en_model.device,text_pipe.model.device,torch.cuda.current_device() )
en_texts=[]
for t in texts:
en_text=translate(zh_en_tokenizer,zh_en_model,t)
en_texts.append(en_text)
zh_en_model.to('cpu')
print("test en_text",en_texts)
# en_text.to("cuda" if torch.cuda.is_available() else "cpu")
pbar.update(1)
for t in en_texts:
if generation=='on':
prompt =text_generate(text_pipe,t,seed)
# 多条,还是单条
lines = prompt.split("\n")
longest_line = max(lines, key=len)
# print(longest_line)
prompt_result.append(longest_line)
else:
prompt_result.append(t)
pbar.update(1)
text_pipe.model.to('cpu')
prompt_result = [correct_prompt_syntax(p) for p in prompt_result]
return {
"ui":{
"prompt": prompt_result
},
"result": (prompt_result,)}
class PromptGenerate:
global _available
available=_available
@classmethod
def INPUT_TYPES(s):
return {"required": {
"text": ("STRING",{"multiline": True,"default": "", "dynamicPrompts": False}),
},
"optional":{
"multiple": (["off","on"],),
"seed":("INT", {"default": 100, "min": 100, "max": 1000000}),
},
}
RETURN_TYPES = ("STRING",)
RETURN_NAMES = ("prompt",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/Prompt"
OUTPUT_NODE = True
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
global text_pipe
text_pipe= None
#
def run(self,text,multiple,seed):
global text_pipe
seed=seed[0]
multiple=multiple[0]
# 进度条
pbar = comfy.utils.ProgressBar(len(text))
text_pipe=pipeline('text-generation', model=text_generator_model_path,device="cuda" if torch.cuda.is_available() else "cpu")
prompt_result=[]
for t in text:
prompt =text_generate(text_pipe,t,seed)
prompt = prompt.split("\n")
if multiple=='off':
prompt = [max(prompt, key=len)]
for p in prompt:
prompt_result.append(p)
pbar.update(1)
text_pipe.model.to('cpu')
return {
"ui":{
"prompt": prompt_result
},
"result": (prompt_result,)}
+162 -61
View File
@@ -1,10 +1,56 @@
import os
import re,random
import os,platform
import re,random,json
from PIL import Image
import numpy as np
# FONT_PATH= os.path.abspath(os.path.join(os.path.dirname(__file__),'../assets/王汉宗颜楷体繁.ttf'))
import folder_paths
import matplotlib.font_manager as fm
import torch
def recursive_search(directory, excluded_dir_names=None):
if not os.path.isdir(directory):
return [], {}
if excluded_dir_names is None:
excluded_dir_names = []
result = []
dirs = {directory: os.path.getmtime(directory)}
for dirpath, subdirs, filenames in os.walk(directory, followlinks=True, topdown=True):
subdirs[:] = [d for d in subdirs if d not in excluded_dir_names]
for file_name in filenames:
relative_path = os.path.relpath(os.path.join(dirpath, file_name), directory)
result.append(relative_path)
for d in subdirs:
path = os.path.join(dirpath, d)
dirs[path] = os.path.getmtime(path)
return result, dirs
def filter_files_extensions(files, extensions):
return sorted(list(filter(lambda a: os.path.splitext(a)[-1].lower() in extensions or len(extensions) == 0, files)))
def get_system_font_path():
ps=[]
system = platform.system()
if system == "Windows":
ps.append(os.path.join(os.environ["WINDIR"], "Fonts"))
elif system == "Darwin":
ps.append(os.path.join("/Library", "Fonts"))
elif system == "Linux":
ps.append(os.path.join("/usr", "share", "fonts"))
ps.append(os.path.join("/usr", "local", "share", "fonts"))
ps=[p for p in ps if os.path.exists(p)]
file_paths=[]
for f in ps:
result, dirs=recursive_search(f)
for r in result:
file_paths.append(r)
file_paths=filter_files_extensions(file_paths,[".otf", ".ttf"])
return file_paths
# import json
# import hashlib
@@ -60,14 +106,14 @@ def get_font_files(directory):
# 尝试获取系统字体
try:
font_paths = fm.findSystemFonts()
for path in font_paths:
font_paths = get_system_font_path()
for file in font_paths:
try:
font_prop = fm.FontProperties(fname=path)
font_name = font_prop.get_name()
font_files[font_name] = path
font_name = os.path.splitext(file)[0]
font_path = file
font_files[font_name] = os.path.abspath(font_path)
except Exception as e:
print(f"Error processing font {path}: {e}")
print(f"Error processing font {file}: {e}")
except Exception as e:
print(f"Error finding system fonts: {e}")
@@ -85,7 +131,12 @@ def flatten_list(nested_list):
if isinstance(item, list):
flat_list.extend(flatten_list(item))
else:
flat_list.append(item)
if torch.is_tensor(item):
print('item.shape',item.shape)
for i in range(item.shape[0]):
flat_list.append(item[i:i + 1, ...])
else:
flat_list.append(item)
return flat_list
@@ -103,7 +154,7 @@ class ColorInput:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/utils"
CATEGORY = "♾️Mixlab/Utils"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,False,False,False,False,)
@@ -132,7 +183,7 @@ class FontInput:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/utils"
CATEGORY = "♾️Mixlab/Utils"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
@@ -162,7 +213,7 @@ class TextToNumber:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/utils"
CATEGORY = "♾️Mixlab/Utils"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
@@ -220,17 +271,18 @@ class FloatSlider:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/utils"
CATEGORY = "♾️Mixlab/Utils"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
def run(self,number,min_value,max_value,step):
def run(self, number, min_value, max_value, step):
if number < min_value:
number= min_value
number = min_value
elif number > max_value:
number= max_value
return (number,)
number = max_value
scaled_number = (number - min_value) / (max_value - min_value)
return (scaled_number,)
class IntNumber:
@@ -272,7 +324,7 @@ class IntNumber:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/utils"
CATEGORY = "♾️Mixlab/Utils"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
@@ -289,11 +341,18 @@ class MultiplicationNode:
def INPUT_TYPES(s):
return {"required": {
"numberA":(any_type,),
"numberB":("FLOAT", {
"multiply_by":("FLOAT", {
"default": 0,
"min": -1, #Minimum value
"min": -2, #Minimum value
"max": 0xffffffffffffffff,
"step": 0.1, #Slider's step
"step": 0.01, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
}),
"add_by":("FLOAT", {
"default": 0,
"min": -2000, #Minimum value
"max": 0xffffffffffffffff,
"step": 0.01, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
})
},
@@ -303,14 +362,14 @@ class MultiplicationNode:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/utils"
CATEGORY = "♾️Mixlab/Utils"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,False,)
def run(self,numberA,numberB):
b=int(numberA*numberB)
a=float(numberA*numberB)
def run(self,numberA,multiply_by,add_by):
b=int(numberA*multiply_by+add_by)
a=float(numberA*multiply_by+add_by)
return (a,b,)
class TextInput:
@@ -325,7 +384,7 @@ class TextInput:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/utils"
CATEGORY = "♾️Mixlab/Utils"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
@@ -399,7 +458,7 @@ class DynamicDelayProcessor:
RETURN_TYPES = (any_type,)
RETURN_NAMES = ('output',)
CATEGORY = "♾️Mixlab/utils"
CATEGORY = "♾️Mixlab/Utils"
def run(self,any_input,delay_seconds,delay_by_text,words_per_seconds,replace_output,replace_value):
# print(f"Delay text:",delay_by_text )
# 获取开始时间戳
@@ -433,12 +492,12 @@ class AppInfo:
def INPUT_TYPES(s):
return {"required": {
"name": ("STRING",{"multiline": False,"default": "Mixlab-App","dynamicPrompts": False}),
"image": ("IMAGE",),
"input_ids":("STRING",{"multiline": True,"default": "\n".join(["1","2","3"]),"dynamicPrompts": False}),
"output_ids":("STRING",{"multiline": True,"default": "\n".join(["5","9"]),"dynamicPrompts": False}),
},
"optional":{
"IMAGE": ("IMAGE",),
"description":("STRING",{"multiline": True,"default": "","dynamicPrompts": False}),
"version":("INT", {
"default": 1,
@@ -450,12 +509,13 @@ class AppInfo:
"share_prefix":("STRING",{"multiline": False,"default": "","dynamicPrompts": False}),
"link":("STRING",{"multiline": False,"default": "https://","dynamicPrompts": False}),
"category":("STRING",{"multiline": False,"default": "","dynamicPrompts": False}),
"auto_save": (["enable","disable"],),
}
}
RETURN_TYPES = ("IMAGE",)
RETURN_NAMES = ("IMAGE",)
RETURN_TYPES = ()
# RETURN_NAMES = ("IMAGE",)
FUNCTION = "run"
@@ -463,11 +523,16 @@ class AppInfo:
OUTPUT_NODE = True
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
# OUTPUT_IS_LIST = (True,)
def run(self,name,image,input_ids,output_ids,description,version,share_prefix,link,category):
def run(self,name,input_ids,output_ids,IMAGE,description,version,share_prefix,link,category,auto_save):
name=name[0]
im=image[0][0]
im=None
if IMAGE:
im=IMAGE[0][0]
#TODO batch 的方式需要处理
im=create_temp_file(im)
# image [img,] img[batch,w,h,a] 列表里面是batch,
input_ids=input_ids[0]
@@ -477,38 +542,14 @@ class AppInfo:
share_prefix=share_prefix[0]
link=link[0]
category=category[0]
#TODO batch 的方式需要处理
im=create_temp_file(im)
# id=get_json_hash([name,im,input_ids,output_ids,description,version])
return {"ui": {"json": [name,im,input_ids,output_ids,description,version,share_prefix,link,category]}, "result": (image,)}
return {"ui": {"json": [name,im,input_ids,output_ids,description,version,share_prefix,link,category]}, "result": ()}
class GetImageSize_:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"image": ("IMAGE",),
}
}
RETURN_TYPES = ("INT", "INT")
RETURN_NAMES = ("width", "height")
FUNCTION = "get_size"
CATEGORY = "♾️Mixlab/utils"
def get_size(self, image):
_, height, width, _ = image.shape
return (width, height)
class SwitchByIndex:
@classmethod
@@ -533,7 +574,7 @@ class SwitchByIndex:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/utils"
CATEGORY = "♾️Mixlab/Utils"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
@@ -544,7 +585,8 @@ class SwitchByIndex:
C=[]
index=index[0]
for a in A:
for a in A:
C.append(a)
for b in B:
C.append(b)
@@ -557,6 +599,7 @@ class SwitchByIndex:
C=[C[index]]
except Exception as e:
C=[]
return (C,)
@@ -589,7 +632,7 @@ class LimitNumber:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/utils"
CATEGORY = "♾️Mixlab/Utils"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
@@ -611,3 +654,61 @@ class LimitNumber:
return (nn,)
class ListStatistics:
@staticmethod
def count_types(lst):
type_count = {}
for item in lst:
item_type = type(item).__name__
if item_type not in type_count:
type_count[item_type] = []
if item_type in ['dict', 'str', 'int', 'float']:
type_count[item_type].append(item)
return type_count
# # 示例列表
# my_list = [1, 'hello', {'name': 'John'}, 3.14, {'age': 25}, 'world', 10]
# # 创建ListStatistics对象
# list_stats = ListStatistics()
# # 调用count_types方法进行统计
# result = list_stats.count_types(my_list)
# # 输出结果
# for item_type, values in result.items():
# print(item_type + ':')
# for value in values:
# print(value)
# print('---')
class TESTNODE_:
@classmethod
def INPUT_TYPES(s):
return {"required": { "ANY":(any_type,), },
}
RETURN_TYPES = (any_type,)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/__TEST"
OUTPUT_NODE = True
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
def run(self,ANY):
print(ANY)
# data=ANY
list_stats = ListStatistics()
# 调用count_types方法进行统计
result = list_stats.count_types(ANY)
return {"ui": {"data": result,"type":[str(type(ANY[0]))]}, "result": (ANY,)}
+2 -2
View File
@@ -145,7 +145,7 @@ class VAELoader:
RETURN_TYPES = ("VAE",)
FUNCTION = "load_vae"
CATEGORY = "♾️Mixlab/_test"
CATEGORY = "♾️Mixlab/__TEST"
#TODO: scale factor?
def load_vae(self, vae_name):
@@ -165,7 +165,7 @@ class VAEDecode:
RETURN_TYPES = ("IMAGE",)
FUNCTION = "decode"
CATEGORY = "♾️Mixlab/_test"
CATEGORY = "♾️Mixlab/__TEST"
def decode(self, vae, samples):
image = vae.decode(samples["samples"].to("cuda:0"))
+288 -21
View File
@@ -303,10 +303,9 @@
}
select,
button,
input {
.card select,
.card button,
.card input {
height: 32px;
cursor: pointer;
background: #000000bf;
@@ -332,14 +331,53 @@
font-size: 12px;
width: 200px;
}
/* 图像编辑器 */
#editor_container {
position: fixed;
top: 0;
z-index: 9;
background: #eee;
height: 100vh;
width: 100%;
display: none;
}
/* 图片的字幕 */
.pswp__custom-caption {
background: rgb(20 27 70);
font-size: 16px;
color: #fff;
width: calc(100% - 32px);
max-width: 400px;
padding: 2px 8px;
border-radius: 4px;
position: absolute;
left: 50%;
bottom: 16px;
transform: translateX(-50%);
}
.pswp__custom-caption a {
color: #fff;
text-decoration: underline;
}
.hidden-caption-content {
display: none;
}
</style>
<!-- <script src="../../../scripts/api.js" type="module"></script> -->
<link href="/extensions/comfyui-mixlab-nodes/lib/photoswipe.min.css" rel="stylesheet">
<link href="/extensions/comfyui-mixlab-nodes/lib/classic.min.css" rel="stylesheet">
<script src="/extensions/comfyui-mixlab-nodes/lib/pickr.min.js"></script>
<script src="/extensions/comfyui-mixlab-nodes/lib/filerobot-image-editor.min.js"></script>
</head>
<body>
<div id="editor_container"></div>
<div style="display: flex;
align-items: center;
justify-content: space-around;">
@@ -353,18 +391,18 @@
<a class="link" href="https://www.mixcomfy.com" target="_blank">ComfyUI中文爱好者社区推荐</a>
</div>
<a target="_blank" href="https://github.com/shadowcz007/comfyui-mixlab-nodes" style="text-decoration: none;
<a target="_blank" href="https://www.mixcomfy.com/blog/" style="text-decoration: none;
color: black;font-size:12px">
<svg height="32" aria-hidden="true" viewBox="0 0 16 16" version="1.1" width="32" data-view-component="true"
class="octicon octicon-mark-github v-align-middle color-fg-default">
<path
d="M8 0c4.42 0 8 3.58 8 8a8.013 8.013 0 0 1-5.45 7.59c-.4.08-.55-.17-.55-.38 0-.27.01-1.13.01-2.2 0-.75-.25-1.23-.54-1.48 1.78-.2 3.65-.88 3.65-3.95 0-.88-.31-1.59-.82-2.15.08-.2.36-1.02-.08-2.12 0 0-.67-.22-2.2.82-.64-.18-1.32-.27-2-.27-.68 0-1.36.09-2 .27-1.53-1.03-2.2-.82-2.2-.82-.44 1.1-.16 1.92-.08 2.12-.51.56-.82 1.28-.82 2.15 0 3.06 1.86 3.75 3.64 3.95-.23.2-.44.55-.51 1.07-.46.21-1.61.55-2.33-.66-.15-.24-.6-.83-1.23-.82-.67.01-.27.38.01.53.34.19.73.9.82 1.13.16.45.68 1.31 2.69.94 0 .67.01 1.3.01 1.49 0 .21-.15.45-.55.38A7.995 7.995 0 0 1 0 8c0-4.42 3.58-8 8-8Z">
</path>
</svg> Code
</svg> Community
</a>
</div>
<!-- <script type="module" src="https://cdn.bootcdn.net/ajax/libs/photoswipe/5.4.0/photoswipe-lightbox.esm.min.js"></script> -->
<script type="module">
import PhotoSwipeLightbox from '/extensions/comfyui-mixlab-nodes/lib/photoswipe-lightbox.esm.min.js'
@@ -406,8 +444,164 @@
return { url: src, name }
};
function base64ToBlob(base64) {
// 去除base64编码中的前缀
const base64WithoutPrefix = base64.replace(/^data:image\/\w+;base64,/, '');
// 将base64编码转换为字节数组
const byteCharacters = atob(base64WithoutPrefix);
// 创建一个存储字节数组的数组
const byteArrays = [];
// 将字节数组放入数组中
for (let offset = 0; offset < byteCharacters.length; offset += 1024) {
const slice = byteCharacters.slice(offset, offset + 1024);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
// 创建blob对象
const blob = new Blob(byteArrays, { type: 'image/png' }); // 根据实际情况设置MIME类型
return blob;
}
function editImage(img, data) {
const update = async () => {
const { imageData } = filerobotImageEditor.getCurrentImgData()
let base64 = imageData.imageBase64;
let fileBlob = base64ToBlob(base64)
// // 获取读取的文件内容,即 Blob 对象
let hashId = await calculateImageHash(fileBlob)
if (hashId == window._appData.data[data.id].hashId) return
let { url, name } = await uploadImage(fileBlob);
// 在这里可以对 Blob 对象进行进一步处理
// imageElement.src = url;
window._appData.data[data.id].inputs.image = name;
window._appData.data[data.id].hashId = hashId;
console.log("上传的文件:", url, data.id, name);
img.src = base64;
}
const { TABS, TOOLS } = FilerobotImageEditor;
const config = {
source: img.src,
// loadableDesignState:{ //默认值
// annotations:{
// watermark:{
// image:'https://127.0.0.1:8189/view?filename=1703554480406.png&type=input&subfolder=&rand=0.044164708320141965',
// width:100,
// height:200,
// x:10,
// y:50,
// name: "Image",
// id:'watermark'
// }
// }
// },
// annotationsCommon: {
// fill: '#ff0000',
// },
// Text: { text: 'Filerobot...' },
Rotate: { angle: 90, componentType: 'slider' },
Crop: {
presetsItems: [
{
titleKey: 'classicTv',
descriptionKey: '4:3',
ratio: 4 / 3,
// icon: CropClassicTv,
},
{
titleKey: 'cinemascope',
descriptionKey: '21:9',
ratio: 21 / 9,
// icon: CropCinemaScope,
},
],
presetsFolders: [
{
titleKey: 'socialMedia', // will be translated into Social Media as backend contains this translation key
// icon: Social, // optional,
groups: [
{
titleKey: 'facebook',
items: [
{
titleKey: 'profile',
width: 180,
height: 180,
descriptionKey: 'fbProfileSize',
},
{
titleKey: 'coverPhoto',
width: 820,
height: 312,
descriptionKey: 'fbCoverPhotoSize',
},
],
},
],
},
],
},
tabsIds: [...Object.values(TABS)], // or ['Adjust', 'Annotate', 'Watermark']
defaultTabId: TABS.WATERMARK, // or 'Annotate'
defaultToolId: TOOLS.WATERMARK, // or 'Text'
closeAfterSave: true
};
let editor = document.querySelector('#editor_container')
// Assuming we have a div with id="editor_container"
editor.style.display = 'block';
// console.log(img,editor,data)
document.body.style.overflow = 'hidden'
const filerobotImageEditor = new FilerobotImageEditor(
editor,
config,
);
filerobotImageEditor.render({
onSave: (editedImageObject, designState) => {
console.log('saved', designState)
//adjustments
update()
},
onClose: (closingReason) => {
console.log('Closing reason', closingReason);
filerobotImageEditor.terminate();
editor.style.display = 'none'
document.body.style.overflow = 'auto'
},
});
}
async function getQueue(clientId) {
try {
@@ -567,19 +761,44 @@
} catch (error) {
isURL = false;
}
let isAElement = undefined;
try {
let div = document.createElement('div');
div.innerHTML = link;
let a = div.querySelector('a');
if (a.href) {
new URL(a.href);
isAElement = div.innerHTML;
}
} catch (error) {
}
// new URL(link)
if (isURL) {
if (isURL || isAElement) {
const linkBtn = document.createElement('button');
// linkBtn.href = link;
linkBtn.innerText = 'go to'
if (isURL) {
// linkBtn.href = link;
linkBtn.innerText = 'go to'
} else if (isAElement) {
linkBtn.innerHTML = isAElement
}
action.appendChild(linkBtn)
linkBtn.style.marginLeft = '18px';
linkBtn.addEventListener('click', e => {
e.preventDefault();
window.open(link);
if (isURL) {
e.preventDefault();
window.open(link);
}
// if(isURL) window.open(link);
})
}
const output_card = document.createElement("div");
output_card.className = 'output_card'
container.appendChild(output_card)
@@ -892,6 +1111,12 @@
btnFromClipboard.innerText = 'paste from clipboard'
actionDiv.appendChild(btnFromClipboard);
const btnForImageEdit = document.createElement("button");
btnForImageEdit.style = ` width: 32px; background: none;margin-left: 18px;`
btnForImageEdit.innerHTML = '<?xml version="1.0" ?><svg version="1.1" style="width: 24px;" viewBox="0 0 50 50" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="Layer_1_1_"><path d="M18.293,31.707h6.414l24-24l-6.414-6.414l-24,24V31.707z M45.879,7.707l-3.586,3.586l-3.586-3.586l3.586-3.586 L45.879,7.707z M20.293,26.121l17-17l3.586,3.586l-17,17h-3.586V26.121z"/><polygon points="43.293,19.707 41.293,19.707 41.293,46.707 3.293,46.707 3.293,8.707 31.293,8.707 31.293,6.707 1.293,6.707 1.293,48.707 43.293,48.707 "/></g></svg>'
actionDiv.appendChild(btnForImageEdit);
uploadContainer.appendChild(actionDiv)
// Create an image element to display the uploaded image
@@ -902,6 +1127,8 @@
btnFromClipboard.addEventListener('click', (event) => handleClipboardImage(imageElement, data));
btnForImageEdit.addEventListener('click', e => editImage(imageElement, data))
uploadImageInput.addEventListener('click', (event) => {
uploadImageInputHide.click()
@@ -985,7 +1212,7 @@
data.inputs.number,
(v) => {
// console.log(data.id,window._appData.data[data.id])
window._appData.data[data.id].inputs.number = v;
window._appData.data[data.id].inputs.number = data.class_type === 'IntNumber' ? parseInt(v) : parseFloat(v);
},
options.min,
options.max,
@@ -1624,6 +1851,7 @@
p.className = 'prompt_image'
p.innerText = prompt;
a.appendChild(p)
img.alt = prompt
}
// imgDiv.parentElement.appendChild(a);
@@ -1834,11 +2062,19 @@
} else if (_images && prompts) {
let url = get_url();
show(Array.from(_images, (img, i) => {
return [`${url}/view?filename=${encodeURIComponent(img.filename)
}&type=${img.type}&subfolder=${encodeURIComponent(img.subfolder)
}&t=${+new Date()}`, prompts[i]];
}), detail.node, 'images_prompts');
let items = [];
// 支持图片的batch
Array.from(_images, (imgs, i) => {
for (const img of imgs) {
items.push([`${url}/view?filename=${encodeURIComponent(img.filename)
}&type=${img.type}&subfolder=${encodeURIComponent(img.subfolder)
}&t=${+new Date()}`, prompts[i]])
}
})
show(items, detail.node, 'images_prompts');
} else if (text) {
ui.output.update("text", Array.isArray(text) ? text.join('\n\n') : text, detail.node)
@@ -1923,6 +2159,36 @@
children: 'a',
pswpModule: () => import('/extensions/comfyui-mixlab-nodes/lib/photoswipe.esm.min.js')
});
lightbox.on('uiRegister', function () {
lightbox.pswp.ui.registerElement({
name: 'custom-caption',
order: 9,
isButton: false,
appendTo: 'root',
html: 'Caption text',
onInit: (el, pswp) => {
lightbox.pswp.on('change', () => {
const currSlideElement = lightbox.pswp.currSlide.data.element
let captionHTML = ''
if (currSlideElement) {
const hiddenCaption = currSlideElement.querySelector(
'.hidden-caption-content'
)
if (hiddenCaption) {
// get caption from element with class hidden-caption-content
captionHTML = hiddenCaption.innerHTML
} else {
// get caption from alt attribute
captionHTML = currSlideElement
.querySelector('img')
.getAttribute('alt')
}
}
el.innerHTML = captionHTML || ''
})
}
})
})
lightbox.init();
// window._lightbox = lightbox
@@ -1998,13 +2264,14 @@
createApp(window._appData);
};
init_app()
</script>
init_app();
</script>
</body>
</html>
+102 -18
View File
@@ -100,16 +100,17 @@ function extractInputAndOutputData (jsonData, inputIds = [], outputIds = []) {
// min max step
options = node.widgets.filter(w => w.type === 'slider')[0].options
// 备选的keywords清单
let ks = getLocalData(`_mixlab_PromptSlide`)
let keywords = ks[id]
// console.log('keywords',keywords)
if (keywords && keywords[0]) {
try {
let keywords = node.widgets.filter(w => w.name === 'upload')[0]
.value
keywords = JSON.parse(keywords)
options.keywords = keywords
} catch (error) {
console.log(error)
}
}
if(node.type=='Color'){
if (node.type == 'Color') {
}
input[inputIds.indexOf(id)] = {
@@ -126,11 +127,11 @@ function extractInputAndOutputData (jsonData, inputIds = [], outputIds = []) {
output[outputIds.indexOf(id)] = { ...data[id], title: node.title, id }
}
if (node.type === 'KSampler'||node.type=='SamplerCustom') {
if (node.type === 'KSampler' || node.type == 'SamplerCustom') {
// seed 的类型收集
try {
seed[id] = node.widgets.filter(
w => (w.name === 'seed'||w.name=='noise_seed')
w => w.name === 'seed' || w.name == 'noise_seed'
)[0].linkedWidgets[0].value
} catch (error) {}
}
@@ -188,7 +189,8 @@ function downloadJsonFile (jsonData, fileName = 'mix_app.json') {
}, 0)
}
async function save (json, download = false) {
async function save (json, download = false, showInfo = true) {
console.log('####SAVE', json[0])
const name = json[0],
version = json[5],
share_prefix = json[6], //用于分享的功能扩展
@@ -237,22 +239,53 @@ async function save (json, download = false) {
if (download) {
await downloadJsonFile(data, data.app.filename)
}
let open = window.confirm(
`You can now access the standalone application on a new page!\n${getUrl()}/mixlab/app?filename=${encodeURIComponent(
data.app.filename
)}&category=${encodeURIComponent(data.app.category)}`
)
if (open)
window.open(
`${getUrl()}/mixlab/app?filename=${encodeURIComponent(
if (showInfo) {
let open = window.confirm(
`You can now access the standalone application on a new page!\n${getUrl()}/mixlab/app?filename=${encodeURIComponent(
data.app.filename
)}&category=${encodeURIComponent(data.app.category)}`
)
if (open)
window.open(
`${getUrl()}/mixlab/app?filename=${encodeURIComponent(
data.app.filename
)}&category=${encodeURIComponent(data.app.category)}`
)
}
} catch (error) {
console.log('###error', error)
}
}
function getInputsAndOutputs () {
const inputs =
`LoadImage CLIPTextEncode PromptSlide TextInput_ Color FloatSlider IntNumber CheckpointLoaderSimple LoraLoader`.split(
' '
),
outputs = `PreviewImage SaveImage ShowTextForGPT VHS_VideoCombine`.split(
' '
)
let inputsId = [],
outputsId = []
for (let node of app.graph._nodes) {
if (inputs.includes(node.type)) {
inputsId.push(node.id)
}
if (outputs.includes(node.type)) {
outputsId.push(node.id)
}
}
return {
input: inputsId,
output: outputsId
}
}
app.registerExtension({
name: 'Mixlab.utils.AppInfo',
async beforeRegisterNodeDef (nodeType, nodeData, app) {
@@ -260,7 +293,16 @@ app.registerExtension({
const orig_nodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = function () {
orig_nodeCreated?.apply(this, arguments)
console.log('#orig_nodeCreated', this)
// console.log('#orig_nodeCreated', this)
// 自动计算workflow里哪些节点支持
let input_ids = this.widgets.filter(w => w.name == 'input_ids')[0],
output_ids = this.widgets.filter(w => w.name == 'output_ids')[0]
const { input, output } = getInputsAndOutputs()
input_ids.value = input.join('\n')
output_ids.value = output.join('\n')
const widget = {
type: 'div',
name: 'AppInfoRun',
@@ -331,6 +373,8 @@ app.registerExtension({
}
this.serialize_widgets = true //需要保存参数
window._mixlab_app_json = null
}
const onExecuted = nodeType.prototype.onExecuted
@@ -339,6 +383,12 @@ app.registerExtension({
console.log(message.json)
window._mixlab_app_json = message.json
try {
let a = this.widgets.filter(w => w.name === 'AppInfoRun')[0]
if (a) {
if (!a.value) a.value = 0
a.value += 1
}
const div = this.widgets.filter(w => w.div)[0].div
Array.from(
div.querySelectorAll('button'),
@@ -347,5 +397,39 @@ app.registerExtension({
} catch (error) {}
}
}
},
async loadedGraphNode (node, app) {
console.log('#loadedGraphNode1111')
window._mixlab_app_json = null //切换workflow需要清空
if (node.type === 'AppInfo') {
let auto_save = node.widgets.filter(w => w.name == 'auto_save')[0]
if (auto_save) {
if (!['enable', 'disable'].includes(auto_save.value)) {
auto_save.value = 'enable'
}
}
}
}
})
api.addEventListener('execution_start', async ({ detail }) => {
console.log('#execution_start', detail)
window._mixlab_app_json = null
})
api.addEventListener('executed', async ({ detail }) => {
console.log('#executed', detail)
// window._mixlab_app_json=null;
const { output } = getInputsAndOutputs()
if (output.includes(parseInt(detail.node))) {
let appinfo = app.graph.findNodesByType('AppInfo')[0]
if (appinfo) {
let auto_save = appinfo.widgets.filter(w => w.name == 'auto_save')[0]
if (auto_save?.value === 'enable') {
// 自动保存
console.log('auto_save')
if (window._mixlab_app_json) save(window._mixlab_app_json, false, false)
}
}
}
})
+1 -1
View File
@@ -3,7 +3,7 @@ import { app } from '../../../scripts/app.js'
const repoOwner = 'shadowcz007' // 替换为仓库的所有者
const repoName = 'comfyui-mixlab-nodes' // 替换为仓库的名称
const version = 'v0.11.0'
const version = 'v0.14.0'
fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
.then(response => response.json())
+79 -25
View File
@@ -3,11 +3,57 @@ import { app } from '../../../scripts/app.js'
import { ComfyWidgets } from '../../../scripts/widgets.js'
import { $el } from '../../../scripts/ui.js'
function getRandomElements (arr, num) {
var result = []
var len = arr.length
for (var i = 0; i < num; i++) {
var randomIndex = Math.floor(Math.random() * len)
result.push(arr[randomIndex])
}
return result
}
const createPrompt = (node, prompts, items, sample) => {
const w = ComfyWidgets['STRING'](
node,
'text',
['STRING', { multiline: true }],
app
).widget
w.inputEl.readOnly = true
w.inputEl.style.opacity = 0.6
w.value = typeof prompts === 'string' ? prompts : prompts.join('\n\n')
const w2 = ComfyWidgets['STRING'](
node,
'text',
['STRING', { multiline: true }],
app
).widget
w2.inputEl.readOnly = true
w2.inputEl.style.opacity = 0.6
w2.value = typeof items === 'string' ? items : JSON.stringify(items, null, 2)
const w3 = ComfyWidgets['STRING'](
node,
'text',
['STRING', { multiline: true }],
app
).widget
w3.inputEl.readOnly = true
w3.inputEl.style.opacity = 0.6
w3.value = typeof sample === 'string' ? sample : sample.join('\n\n')
}
app.registerExtension({
name: 'Mixlab.prompt.ClipInterrogator',
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeData.name === 'ClipInterrogator') {
function populate (prompts, items) {
function populate (prompts, items, random_samples) {
if (this.widgets) {
for (let i = 0; i < this.widgets.length; i++) {
if (this.widgets[i].type !== 'combo') this.widgets[i].onRemove?.()
@@ -15,29 +61,9 @@ app.registerExtension({
this.widgets.length = 2
}
const w = ComfyWidgets['STRING'](
this,
'text',
['STRING', { multiline: true }],
app
).widget
w.inputEl.readOnly = true
w.inputEl.style.opacity = 0.6
createPrompt(this, prompts, items, random_samples)
w.value = prompts.join('\n\n')
const w2 = ComfyWidgets['STRING'](
this,
'text',
['STRING', { multiline: true }],
app
).widget
w2.inputEl.readOnly = true
w2.inputEl.style.opacity = 0.6
w2.value = JSON.stringify(items, null, 2)
console.log('ClipInterrogator',w,w2)
// console.log('ClipInterrogator', w, w2)
requestAnimationFrame(() => {
const sz = this.computeSize()
if (sz[0] < this.size[0]) {
@@ -55,11 +81,39 @@ app.registerExtension({
const onExecuted = nodeType.prototype.onExecuted
nodeType.prototype.onExecuted = function (message) {
onExecuted?.apply(this, arguments)
console.log('##', message)
populate.call(this, message.prompt, message.analysis)
// console.log('##', message)
populate.call(
this,
message.prompt,
message.analysis,
message.random_samples
)
}
this.serialize_widgets = true //需要保存参数
}
},
async loadedGraphNode (node, app) {
// Fires every time a node is constructed
// You can modify widgets/add handlers/etc here
if (node.type === 'ClipInterrogator') {
try {
let widgets_values = node.widgets_values
console.log(widgets_values )
try {
if (widgets_values[2] && widgets_values[3] && widgets_values[4])
createPrompt(
node,
widgets_values[2],
widgets_values[3],
widgets_values[4]
)
} catch (error) {
console.log(error)
}
} catch (error) {}
}
}
})
+9 -3
View File
@@ -1331,7 +1331,13 @@ app.registerExtension({
let w = 360,
s = widget.preview.videoWidth / widget.preview.videoHeight,
h = w / s || w
console.log(h)
// console.log(h)
if (!window.documentPictureInPicture) {
window.alert(
'This feature is available only in secure contexts (HTTPS), in some or all supporting browsers. https://developer.mozilla.org/en-US/docs/Web/API/Document_Picture-in-Picture_API'
)
}
const pipWindow = await documentPictureInPicture.requestWindow({
width: w,
@@ -2137,8 +2143,8 @@ const node = {
loadedGraphNode (node, app) {
if (node.type === 'RandomPrompt') {
try {
let max_count = node.widgets.filter(w => w.name === "max_count")[0];
max_count.value= node.widgets_values[0]
let max_count = node.widgets.filter(w => w.name === 'max_count')[0]
max_count.value = node.widgets_values[0]
// console.log('RandomPrompt',max_count,node.widgets_values[0])
} catch (error) {
console.log(error)
+230 -30
View File
@@ -3,6 +3,87 @@ import { api } from '../../../scripts/api.js'
import { ComfyWidgets } from '../../../scripts/widgets.js'
import { $el } from '../../../scripts/ui.js'
import PhotoSwipeLightbox from '/extensions/comfyui-mixlab-nodes/lib/photoswipe-lightbox.esm.min.js'
function loadCSS (url) {
var link = document.createElement('link')
link.rel = 'stylesheet'
link.type = 'text/css'
link.href = url
document.getElementsByTagName('head')[0].appendChild(link)
// Create a style element
const style = document.createElement('style')
// Define the CSS rule for scrollbar width
const cssRule = `.pswp__custom-caption {
background: rgb(20 27 70);
font-size: 16px;
color: #fff;
width: calc(100% - 32px);
max-width: 980px;
padding: 2px 8px;
border-radius: 4px;
position: absolute;
left: 50%;
bottom: 16px;
transform: translateX(-50%);
}
.pswp__custom-caption a {
color: #fff;
text-decoration: underline;
}
.hidden-caption-content {
display: none;
}`
// Add the CSS rule to the style element
style.appendChild(document.createTextNode(cssRule))
// Append the style element to the document head
document.head.appendChild(style)
}
loadCSS('/extensions/comfyui-mixlab-nodes/lib/photoswipe.min.css')
function initLightBox () {
const lightbox = new PhotoSwipeLightbox({
gallery: '.prompt_image_output',
children: 'a',
pswpModule: () =>
import('/extensions/comfyui-mixlab-nodes/lib/photoswipe.esm.min.js')
})
lightbox.on('uiRegister', function () {
lightbox.pswp.ui.registerElement({
name: 'custom-caption',
order: 9,
isButton: false,
appendTo: 'root',
html: 'Caption text',
onInit: (el, pswp) => {
lightbox.pswp.on('change', () => {
const currSlideElement = lightbox.pswp.currSlide.data.element
let captionHTML = ''
if (currSlideElement) {
const hiddenCaption = currSlideElement.querySelector(
'.hidden-caption-content'
)
if (hiddenCaption) {
// get caption from element with class hidden-caption-content
captionHTML = hiddenCaption.innerHTML
} else {
// get caption from alt attribute
captionHTML = currSlideElement
.querySelector('img')
.getAttribute('alt')
}
}
el.innerHTML = captionHTML || ''
})
}
})
})
lightbox.init()
}
function get_position_style (ctx, widget_width, y, node_height) {
const MARGIN = 4 // the margin around the html element
@@ -47,7 +128,7 @@ async function fetchImage (url) {
try {
const response = await fetch(url)
const blob = await response.blob()
return blob
} catch (error) {
console.error('出现错误:', error)
@@ -90,6 +171,106 @@ const createSelect = (select, opts, targetWidget) => {
})
}
app.registerExtension({
name: 'Mixlab.prompt.RandomPrompt',
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == 'RandomPrompt') {
const orig_nodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = async function () {
orig_nodeCreated?.apply(this, arguments)
const mutable_prompt = this.widgets.filter(
w => w.name == 'mutable_prompt'
)[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 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]
this.title = file.name.split('.')[0]
// console.log(file.name.split('.')[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)
mutable_prompt.value = keywords.join('\n')
inp.remove()
}
// 以文本方式读取文件
reader.readAsText(file)
})
})
widget.div.appendChild(btn)
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 === 'RandomPrompt') {
}
}
})
app.registerExtension({
name: 'Mixlab.prompt.PromptSlide',
async beforeRegisterNodeDef (nodeType, nodeData, app) {
@@ -167,10 +348,11 @@ app.registerExtension({
// 打印文件内容
// console.log(keywords)
// widget.value = keywords
let ks = getLocalData(`_mixlab_PromptSlide`)
ks[this.id] = keywords
setLocalDataOfWin(`_mixlab_PromptSlide`, ks)
widget.value = JSON.stringify(keywords)
// let ks = getLocalData(`_mixlab_PromptSlide`)
// ks[this.id] = keywords
// setLocalDataOfWin(`_mixlab_PromptSlide`, ks)
createSelect(select, keywords, prompt_keyword)
@@ -205,14 +387,13 @@ app.registerExtension({
if (node.type === 'PromptSlide') {
try {
let prompt = node.widgets.filter(w => w.name === 'prompt_keyword')[0]
let ks = getLocalData(`_mixlab_PromptSlide`)
let keywords = ks[node.id]
// let ks = getLocalData(`_mixlab_PromptSlide`)
let uploadWidget = node.widgets.filter(w => w.name == 'upload')[0]
// console.log('##widget', uploadWidget.value)
let keywords = JSON.parse(uploadWidget.value)
// 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)
}
@@ -221,17 +402,17 @@ app.registerExtension({
}
})
app.registerExtension({
name: 'Mixlab.prompt.PromptImage',
_createResult: async (node, widget, message) => {
widget.div.innerHTML = ``
const _createResult = async (node, widget, message) => {
widget.div.innerHTML = ``
const width = node.size[0] * 0.5 - 12
const width = node.size[0] * 0.5 - 12
let height_add = 0
let height_add = 0
for (let index = 0; index < message._images.length; index++) {
const img = message._images[index]
for (let index = 0; index < message._images.length; index++) {
const imgs = message._images[index]
for (const img of imgs) {
let url = api.apiURL(
`/view?filename=${encodeURIComponent(img.filename)}&type=${
img.type
@@ -262,7 +443,6 @@ app.registerExtension({
app.graph.change()
}
// const blob = item.getAsFile();
imageNode.pasteFile(blob)
}
@@ -272,20 +452,33 @@ app.registerExtension({
let h = (image.naturalHeight * width) / image.naturalWidth
if (index % 2 === 0) height_add += h
div.style = `width: ${width}px;height:${h}px;position: relative;margin: 4px;`
div.innerHTML = `<img src="${url}" style='width: 100%'/>
<p style="position: absolute;
div.innerHTML = `<a href="${url}"
data-pswp-width="${image.naturalWidth}"
data-pswp-height="${image.naturalHeight}"
target="_blank">
<img src="${url}" style='width: 100%' alt="${message.prompts[index]}"/>
</a>
<p style="position: absolute;
bottom: 0;
left: 0;
background:#444444c2;
left: 0;
opacity: 0.6;
background-color: var(--comfy-input-bg);
color: var(--descrip-text);
margin: 0;
font-size: 12px;
padding: 5px;
text-align: left;">${message.prompts[index]}</p>`
widget.div.appendChild(div)
}
}
node.size[1] = 98 + height_add
}
app.registerExtension({
name: 'Mixlab.prompt.PromptImage',
node.size[1] = 98 + height_add
},
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == 'PromptImage') {
const orig_nodeCreated = nodeType.prototype.onNodeCreated
@@ -308,11 +501,14 @@ app.registerExtension({
}
widget.div = $el('div', {})
widget.div.className = 'prompt_image_output'
document.body.appendChild(widget.div)
this.addCustomWidget(widget)
initLightBox()
const onRemoved = this.onRemoved
this.onRemoved = () => {
widget.div.remove()
@@ -339,14 +535,15 @@ app.registerExtension({
const onExecuted = nodeType.prototype.onExecuted
nodeType.prototype.onExecuted = async function (message) {
onExecuted?.apply(this, arguments)
console.log('PromptImage', message.prompts, message._images)
console.log('#PromptImage', message.prompts, message._images)
// window._mixlab_app_json = message.json
try {
let widget = this.widgets.filter(w => w.name === 'result')[0]
widget.value = message
this._createResult(this, widget, message)
} catch (error) {}
_createResult(this, widget, { ...message })
} catch (error) {
console.log(error)
}
}
this.serialize_widgets = true //需要保存参数
@@ -357,10 +554,13 @@ app.registerExtension({
// await sleep(0)
let widget = node.widgets.filter(w => w.name === 'result')[0]
console.log('widget.value', widget.value)
initLightBox()
let cards = widget.div.querySelectorAll('.card')
if (cards.length == 0) node.size = [280, 120]
this._createResult(node, widget, widget.value)
_createResult(node, widget, widget.value)
}
}
})
+4 -2
View File
@@ -739,8 +739,9 @@ app.registerExtension({
if (category === '0') {
apps_opts.push(
...Array.from(apps_map[category], a => {
// console.log('#1级',a)
return {
content: a.name,
content: `${a.name}_${a.version}`,
has_submenu: false,
callback: async () => {
try {
@@ -768,8 +769,9 @@ app.registerExtension({
disabled: false,
submenu: {
options: Array.from(apps_map[category], a => {
// console.log('#二级',a)
return {
content: a.name,
content: `${a.name}_${a.version}`,
callback: async () => {
try {
let item = (await get_my_app(a.filename, a.category))[0]
+89 -3
View File
@@ -159,7 +159,7 @@ app.registerExtension({
el: `#${inputColor.id}`,
theme: 'classic', // or 'monolith', or 'nano'
// closeOnScroll: true,
default:'#000000',
default: '#000000',
swatches: [
'rgba(244, 67, 54, 1)',
'rgba(233, 30, 99, 0.95)',
@@ -257,7 +257,6 @@ app.registerExtension({
app.registerExtension({
name: 'Mixlab.utils.TextToNumber',
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == 'TextToNumber') {
const onExecuted = nodeType.prototype.onExecuted
@@ -270,9 +269,96 @@ app.registerExtension({
const n = this.widgets.filter(w => w.name === 'number')[0]
n.value = message.num[0]
}
console.log('TextToNumber', random_number.value)
}
}
}
})
const min_max = node => {
if(node.widgets){
const min_value = node.widgets.filter(w => w.name === 'min_value')[0]
const max_value = node.widgets.filter(w => w.name === 'max_value')[0]
const number = node.widgets.filter(w => w.name === 'number')[0]
if (number) {
number.options.min = min_value.value
number.options.max = max_value.value
number.value = Math.min(number.options.max, number.value)
number.value = Math.max(number.options.min, number.value)
}
if (min_value)
min_value.callback = e => {
number.options.min = e
number.value = e
}
if (max_value)
max_value.callback = e => {
number.options.max = e
number.value = e
}
}
}
app.registerExtension({
name: 'Mixlab.utils.FloatSlider',
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == 'FloatSlider') {
const orig_nodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
orig_nodeCreated?.apply(this, arguments)
min_max(this)
}
}
},
async loadedGraphNode (node, app) {
if (node.type === 'FloatSlider') {
min_max(node)
}
}
})
app.registerExtension({
name: 'Mixlab.utils.IntNumber',
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == 'IntNumber') {
const orig_nodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = function () {
orig_nodeCreated?.apply(this, arguments)
min_max(this)
}
}
},
async loadedGraphNode (node, app) {
if (node.type === 'IntNumber') {
min_max(node)
}
}
})
app.registerExtension({
name: 'Mixlab.utils.TESTNODE_',
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == 'TESTNODE_') {
const onExecuted = nodeType.prototype.onExecuted;
nodeType.prototype.onExecuted = function (message) {
onExecuted?.apply(this, arguments);
console.log('##',message)
};
}
},
})
+59 -30
View File
@@ -60,30 +60,7 @@ app.registerExtension({
type: inputData[0], // the type, CHEESE
name: inputName, // the name, slice
size: [128, 24], // a default size
draw (ctx, node, width, y) {
// // 绘制文件图标的函数
// function drawFileIcon () {
// // 清空画布
// // ctx.clearRect(0, 0, canvas.width, canvas.height)
// // 绘制文件外框
// ctx.fillStyle = '#000'
// ctx.fillRect(5, 5, 40, 40)
// // 绘制文件夹图标
// ctx.fillStyle = '#f00'
// ctx.fillRect(10, 15, 30, 20)
// // 绘制监听符号
// ctx.beginPath()
// ctx.arc(30, 35, 5, 0, 2 * Math.PI)
// ctx.fillStyle = '#00f'
// ctx.fill()
// }
// // 调用绘制函数
// drawFileIcon()
},
draw (ctx, node, width, y) {},
computeSize (...args) {
return [128, 24] // a method to compute the current size of the widget
},
@@ -124,11 +101,19 @@ app.registerExtension({
nodeType.prototype.onNodeCreated = function () {
orig_nodeCreated?.apply(this, arguments)
console.log('watch widtget', this.widgets)
// 虚拟的widget,用于更新节点,让其每次都运行
const widget = {
type: 'div',
name: 'seed',
draw (ctx, node, widget_width, y, widget_height) {}
}
this.addCustomWidget(widget)
const watcher = this.widgets.filter(w => w.name == 'watcher')[0]
watcher.callback = () => {
console.log('watcher', watcher.value)
if (watcher.value === 'enable') {
if (window._mixlab_watcher_t)
clearInterval(window._mixlab_watcher_t)
@@ -140,7 +125,7 @@ app.registerExtension({
window._mixlab_file_path_watcher = json.event_type
// widget.card.innerText = window._mixlab_file_path_watcher || ''
//运行
document.querySelector('#queue-button').click()
// document.querySelector('#queue-button').click()
}
})
}, 1000)
@@ -162,15 +147,59 @@ app.registerExtension({
window._mixlab_file_path_watcher = json.event_type
})
/*
Add the widget, make sure we clean up nicely, and we do not want to be serialized!
*/
// this.addCustomWidget(widget)
this.onRemoved = function () {
// widget.card.remove()
}
this.serialize_widgets = true
}
const onExecuted = nodeType.prototype.onExecuted
nodeType.prototype.onExecuted = function (message) {
onExecuted?.apply(this, arguments)
console.log(message)
try {
let seed = this.widgets.filter(w => w.name === 'seed')[0]
if (seed) {
if (!seed.value) seed.value = 0
seed.value += 1
}
} catch (error) {}
}
}
},
async loadedGraphNode (node, app) {
if (node.type === 'LoadImagesFromPath') {
const watcher = node.widgets.filter(w => w.name == 'watcher')[0]
if (watcher) {
if (watcher.value === 'enable') {
if (window._mixlab_watcher_t) clearInterval(window._mixlab_watcher_t)
window._mixlab_watcher_t = setInterval(() => {
// 上次路径填充
getConfig().then(json => {
console.log(json.event_type)
if (json.event_type != window._mixlab_file_path_watcher) {
window._mixlab_file_path_watcher = json.event_type
// widget.card.innerText = window._mixlab_file_path_watcher || ''
//运行
document.querySelector('#queue-button').click()
}
})
}, 1000)
} else {
if (window._mixlab_watcher_t) {
clearInterval(window._mixlab_watcher_t)
}
window._mixlab_watcher_t = null
}
}
try {
let seed = node.widgets.filter(w => w.name === 'seed')[0]
if (seed) {
if (!seed.value) seed.value = 0
seed.value += 1
}
} catch (error) {}
}
}
})
File diff suppressed because one or more lines are too long
+736
View File
@@ -0,0 +1,736 @@
{
"last_node_id": 29,
"last_link_id": 32,
"nodes": [
{
"id": 7,
"type": "CLIPTextEncode",
"pos": [
108,
316
],
"size": {
"0": 425.27801513671875,
"1": 180.6060791015625
},
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 16
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
6
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"text, watermark"
]
},
{
"id": 18,
"type": "ControlNetApply",
"pos": [
485,
792
],
"size": {
"0": 317.4000244140625,
"1": 98
},
"flags": {},
"order": 10,
"mode": 0,
"inputs": [
{
"name": "conditioning",
"type": "CONDITIONING",
"link": 22,
"slot_index": 0
},
{
"name": "control_net",
"type": "CONTROL_NET",
"link": 18,
"slot_index": 1
},
{
"name": "image",
"type": "IMAGE",
"link": 26
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
21
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "ControlNetApply"
},
"widgets_values": [
1
]
},
{
"id": 6,
"type": "CLIPTextEncode",
"pos": [
117,
95
],
"size": {
"0": 422.84503173828125,
"1": 164.31304931640625
},
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "clip",
"type": "CLIP",
"link": 15
}
],
"outputs": [
{
"name": "CONDITIONING",
"type": "CONDITIONING",
"links": [
22
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "CLIPTextEncode"
},
"widgets_values": [
"a future city,buiding,future,magic,under water"
]
},
{
"id": 15,
"type": "LoraLoader",
"pos": [
116,
-113
],
"size": {
"0": 315,
"1": 126
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 13
},
{
"name": "clip",
"type": "CLIP",
"link": 14
}
],
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
12,
23
],
"shape": 3,
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [
15,
16
],
"shape": 3,
"slot_index": 1
}
],
"properties": {
"Node name for S&R": "LoraLoader"
},
"widgets_values": [
"lcm-lora-sdv1-5.safetensors",
1,
1
]
},
{
"id": 4,
"type": "CheckpointLoaderSimple",
"pos": [
-380,
185
],
"size": {
"0": 315,
"1": 98
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "MODEL",
"type": "MODEL",
"links": [
13
],
"slot_index": 0
},
{
"name": "CLIP",
"type": "CLIP",
"links": [
14
],
"slot_index": 1
},
{
"name": "VAE",
"type": "VAE",
"links": [
8
],
"slot_index": 2
}
],
"properties": {
"Node name for S&R": "CheckpointLoaderSimple"
},
"widgets_values": [
"deliberate_v2.safetensors"
]
},
{
"id": 19,
"type": "DiffControlNetLoader",
"pos": [
40,
792
],
"size": {
"0": 367.8165283203125,
"1": 58.083831787109375
},
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 23,
"slot_index": 0
}
],
"outputs": [
{
"name": "CONTROL_NET",
"type": "CONTROL_NET",
"links": [
18
],
"shape": 3
}
],
"properties": {
"Node name for S&R": "DiffControlNetLoader"
},
"widgets_values": [
"control_v11f1p_sd15_depth.pth"
]
},
{
"id": 5,
"type": "EmptyLatentImage",
"pos": [
65,
573
],
"size": {
"0": 315,
"1": 106
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
2
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "EmptyLatentImage"
},
"widgets_values": [
512,
512,
1
]
},
{
"id": 25,
"type": "LeReS-DepthMapPreprocessor",
"pos": [
47,
904
],
"size": {
"0": 369.6000061035156,
"1": 130
},
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 31
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
26,
27
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "LeReS-DepthMapPreprocessor"
},
"widgets_values": [
0.1,
0,
"disable",
512
]
},
{
"id": 3,
"type": "KSampler",
"pos": [
1100,
-97
],
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 11,
"mode": 0,
"inputs": [
{
"name": "model",
"type": "MODEL",
"link": 12,
"slot_index": 0
},
{
"name": "positive",
"type": "CONDITIONING",
"link": 21
},
{
"name": "negative",
"type": "CONDITIONING",
"link": 6
},
{
"name": "latent_image",
"type": "LATENT",
"link": 2
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
7
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "KSampler"
},
"widgets_values": [
170633013599955,
"fixed",
4,
1.6,
"lcm",
"simple",
1
]
},
{
"id": 8,
"type": "VAEDecode",
"pos": [
1100,
-241
],
"size": {
"0": 210,
"1": 46
},
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 7
},
{
"name": "vae",
"type": "VAE",
"link": 8
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
28
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
},
{
"id": 20,
"type": "PreviewImage",
"pos": [
492,
955
],
"size": {
"0": 526.9624633789062,
"1": 367.3833923339844
},
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 27
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 26,
"type": "FloatingVideo",
"pos": [
1565,
-181
],
"size": [
315,
58
],
"flags": {},
"order": 13,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 28
}
],
"properties": {
"Node name for S&R": "FloatingVideo"
},
"widgets_values": [
null
]
},
{
"id": 28,
"type": "PreviewImage",
"pos": [
-241,
953
],
"size": {
"0": 210,
"1": 246
},
"flags": {},
"order": 5,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 32
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 29,
"type": "ScreenShare",
"pos": [
-639,
366
],
"size": [
315,
644
],
"flags": {},
"order": 2,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
31,
32
],
"shape": 3,
"slot_index": 0
},
{
"name": "PROMPT",
"type": "STRING",
"links": null,
"shape": 3
},
{
"name": "FLOAT",
"type": "FLOAT",
"links": null,
"shape": 3
},
{
"name": "INT",
"type": "INT",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "ScreenShare"
},
"widgets_values": [
null,
500,
null,
null,
null,
null
]
}
],
"links": [
[
2,
5,
0,
3,
3,
"LATENT"
],
[
6,
7,
0,
3,
2,
"CONDITIONING"
],
[
7,
3,
0,
8,
0,
"LATENT"
],
[
8,
4,
2,
8,
1,
"VAE"
],
[
12,
15,
0,
3,
0,
"MODEL"
],
[
13,
4,
0,
15,
0,
"MODEL"
],
[
14,
4,
1,
15,
1,
"CLIP"
],
[
15,
15,
1,
6,
0,
"CLIP"
],
[
16,
15,
1,
7,
0,
"CLIP"
],
[
18,
19,
0,
18,
1,
"CONTROL_NET"
],
[
21,
18,
0,
3,
1,
"CONDITIONING"
],
[
22,
6,
0,
18,
0,
"CONDITIONING"
],
[
23,
15,
0,
19,
0,
"MODEL"
],
[
26,
25,
0,
18,
2,
"IMAGE"
],
[
27,
25,
0,
20,
0,
"IMAGE"
],
[
28,
8,
0,
26,
0,
"IMAGE"
],
[
31,
29,
0,
25,
0,
"IMAGE"
],
[
32,
29,
0,
28,
0,
"IMAGE"
]
],
"groups": [],
"config": {},
"extra": {},
"version": 0.4
}
+57 -57
View File
@@ -1,6 +1,6 @@
{
"last_node_id": 23,
"last_link_id": 51,
"last_node_id": 22,
"last_link_id": 48,
"nodes": [
{
"id": 7,
@@ -105,7 +105,7 @@
{
"name": "text",
"type": "STRING",
"link": 51,
"link": 48,
"widget": {
"name": "text"
}
@@ -295,7 +295,7 @@
"Node name for S&R": "KSampler"
},
"widgets_values": [
482859286431021,
644769503212755,
"randomize",
4,
1.6,
@@ -316,19 +316,47 @@
"1": 246
},
"flags": {},
"order": 7,
"order": 6,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 50
"link": 46
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 20,
"type": "FloatingVideo",
"pos": [
2041,
277
],
"size": [
315,
58
],
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 41
}
],
"properties": {
"Node name for S&R": "FloatingVideo"
},
"widgets_values": [
null
]
},
{
"id": 6,
"type": "LoraLoader",
@@ -445,13 +473,13 @@
"1": 58
},
"flags": {},
"order": 6,
"order": 7,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 49
"link": 47
}
],
"outputs": [
@@ -473,43 +501,15 @@
]
},
{
"id": 20,
"type": "FloatingVideo",
"id": 22,
"type": "ScreenShare",
"pos": [
1928,
295
-111,
427
],
"size": [
315,
58
],
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 41
}
],
"properties": {
"Node name for S&R": "FloatingVideo"
},
"widgets_values": [
null
]
},
{
"id": 23,
"type": "ScreenShare",
"pos": [
-65,
446
],
"size": [
312.78457519531213,
606.2132135620109
644
],
"flags": {},
"order": 3,
@@ -519,8 +519,8 @@
"name": "IMAGE",
"type": "IMAGE",
"links": [
49,
50
46,
47
],
"shape": 3,
"slot_index": 0
@@ -529,7 +529,7 @@
"name": "PROMPT",
"type": "STRING",
"links": [
51
48
],
"shape": 3,
"slot_index": 1
@@ -552,7 +552,7 @@
},
"widgets_values": [
null,
2018,
500,
null,
null,
null,
@@ -810,24 +810,24 @@
"CLIP"
],
[
49,
23,
0,
18,
0,
"IMAGE"
],
[
50,
23,
46,
22,
0,
2,
0,
"IMAGE"
],
[
51,
23,
47,
22,
0,
18,
0,
"IMAGE"
],
[
48,
22,
1,
8,
1,
@@ -1,6 +1,6 @@
{
"last_node_id": 128,
"last_link_id": 207,
"last_node_id": 129,
"last_link_id": 208,
"nodes": [
{
"id": 8,
@@ -466,7 +466,7 @@
"1": 262
},
"flags": {},
"order": 28,
"order": 27,
"mode": 0,
"inputs": [
{
@@ -526,7 +526,7 @@
"1": 262
},
"flags": {},
"order": 30,
"order": 29,
"mode": 0,
"inputs": [
{
@@ -574,31 +574,6 @@
""
]
},
{
"id": 101,
"type": "PreviewImage",
"pos": [
5328.397364750004,
-1726.0537115390628
],
"size": {
"0": 210,
"1": 246
},
"flags": {},
"order": 27,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 161
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 21,
"type": "Note",
@@ -670,7 +645,7 @@
"1": 262
},
"flags": {},
"order": 32,
"order": 31,
"mode": 0,
"inputs": [
{
@@ -1148,7 +1123,7 @@
"1": 46
},
"flags": {},
"order": 34,
"order": 33,
"mode": 0,
"inputs": [
{
@@ -1171,6 +1146,12 @@
],
"shape": 3,
"slot_index": 0
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3
}
],
"properties": {
@@ -1189,7 +1170,7 @@
"1": 46
},
"flags": {},
"order": 35,
"order": 34,
"mode": 0,
"inputs": [
{
@@ -1212,6 +1193,12 @@
],
"shape": 3,
"slot_index": 0
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3
}
],
"properties": {
@@ -1230,7 +1217,7 @@
"1": 46
},
"flags": {},
"order": 36,
"order": 35,
"mode": 0,
"inputs": [
{
@@ -1253,6 +1240,12 @@
],
"shape": 3,
"slot_index": 0
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3
}
],
"properties": {
@@ -1304,7 +1297,7 @@
"1": 532.8897705078125
},
"flags": {},
"order": 37,
"order": 36,
"mode": 0,
"inputs": [
{
@@ -1329,7 +1322,7 @@
"1": 553.1112670898438
},
"flags": {},
"order": 38,
"order": 37,
"mode": 0,
"inputs": [
{
@@ -1354,7 +1347,7 @@
"1": 150
},
"flags": {},
"order": 31,
"order": 30,
"mode": 0,
"inputs": [
{
@@ -1464,7 +1457,7 @@
"1": 499.6607971191406
},
"flags": {},
"order": 39,
"order": 38,
"mode": 0,
"inputs": [
{
@@ -1489,7 +1482,7 @@
"1": 150
},
"flags": {},
"order": 33,
"order": 32,
"mode": 0,
"inputs": [
{
@@ -1554,7 +1547,7 @@
"1": 150
},
"flags": {},
"order": 29,
"order": 28,
"mode": 0,
"inputs": [
{
@@ -1705,56 +1698,10 @@
101,
0,
"#000000",
false,
false
]
},
{
"id": 98,
"type": "AppInfo",
"pos": [
4922.397364750004,
-1807.0537115390628
],
"size": {
"0": 362.42608642578125,
"1": 431.3537902832031
},
"flags": {},
"order": 16,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 162
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
161
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "AppInfo"
},
"widgets_values": [
"AIPC大赛模板",
"\n75\n118",
"57\n126\n127\n",
"投稿模板#发挥你的想象力",
4,
"#AIPC# 参赛作品",
"https://www.weibo.com",
"",
null
]
},
{
"id": 106,
"type": "LoadImage",
@@ -1762,10 +1709,10 @@
4535,
-1843
],
"size": [
210,
314
],
"size": {
"0": 210,
"1": 314
},
"flags": {},
"order": 7,
"mode": 0,
@@ -1774,7 +1721,7 @@
"name": "IMAGE",
"type": "IMAGE",
"links": [
162
208
],
"shape": 3,
"slot_index": 0
@@ -1835,6 +1782,43 @@
"1703665898041.png",
"image"
]
},
{
"id": 129,
"type": "AppInfo",
"pos": [
4893,
-1778
],
"size": {
"0": 400,
"1": 344.0000305175781
},
"flags": {},
"order": 16,
"mode": 0,
"inputs": [
{
"name": "LOGO",
"type": "IMAGE",
"link": 208
}
],
"properties": {
"Node name for S&R": "AppInfo"
},
"widgets_values": [
"AIPC大赛模板",
"\n75\n118",
"57\n126\n127\n",
"投稿模板#发挥你的想象力",
4,
"#AIPC# 参赛作品",
"https://www.weibo.com",
"",
"enable",
1
]
}
],
"links": [
@@ -2174,22 +2158,6 @@
0,
"IMAGE"
],
[
161,
98,
0,
101,
0,
"IMAGE"
],
[
162,
106,
0,
98,
0,
"IMAGE"
],
[
185,
118,
@@ -2333,6 +2301,14 @@
127,
0,
"IMAGE"
],
[
208,
106,
0,
129,
0,
"IMAGE"
]
],
"groups": [
@@ -2351,10 +2327,10 @@
{
"title": "Group",
"bounding": [
4912,
4883,
-2035,
637,
669
666,
611
],
"color": "#444",
"font_size": 24,
+133 -135
View File
@@ -1,6 +1,6 @@
{
"last_node_id": 22,
"last_link_id": 23,
"last_node_id": 23,
"last_link_id": 25,
"nodes": [
{
"id": 9,
@@ -167,48 +167,6 @@
"beautiful scenery nature glass bottle landscape, , purple galaxy bottle,"
]
},
{
"id": 10,
"type": "VAEDecode",
"pos": [
2870,
630
],
"size": {
"0": 210,
"1": 46
},
"flags": {
"collapsed": false
},
"order": 11,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 8
},
{
"name": "vae",
"type": "VAE",
"link": 9
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
12
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
},
{
"id": 16,
"type": "ShowTextForGPT",
@@ -248,31 +206,6 @@
"a girl face,super,(Pop Art:1.26),(Black and White:1.26)"
]
},
{
"id": 11,
"type": "PreviewImage",
"pos": [
3319,
-161
],
"size": {
"0": 435.8727111816406,
"1": 511.8609619140625
},
"flags": {},
"order": 13,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 13
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 5,
"type": "KSampler",
@@ -349,7 +282,7 @@
],
"size": {
"0": 315,
"1": 58
"1": 130
},
"flags": {},
"order": 2,
@@ -370,7 +303,10 @@
"Node name for S&R": "IntNumber"
},
"widgets_values": [
-1
0,
0,
1,
1
]
},
{
@@ -382,7 +318,7 @@
],
"size": {
"0": 315,
"1": 82
"1": 106
},
"flags": {},
"order": 3,
@@ -403,51 +339,7 @@
},
"widgets_values": [
"Pop Art",
1.26
]
},
{
"id": 12,
"type": "AppInfo",
"pos": [
2924,
846
],
"size": {
"0": 400,
"1": 272
},
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 12
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
13
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "AppInfo"
},
"widgets_values": [
"Prompt-weight-test",
"21\n22\n18",
"11\n16",
"",
1,
"",
1.26,
null
]
},
@@ -460,7 +352,7 @@
],
"size": {
"0": 315,
"1": 82
"1": 106
},
"flags": {},
"order": 4,
@@ -481,7 +373,8 @@
},
"widgets_values": [
"Black and White",
1.26
1.26,
null
]
},
{
@@ -584,6 +477,111 @@
"disable",
""
]
},
{
"id": 11,
"type": "PreviewImage",
"pos": [
3449,
-102
],
"size": {
"0": 435.8727111816406,
"1": 511.8609619140625
},
"flags": {},
"order": 12,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": 24
}
],
"properties": {
"Node name for S&R": "PreviewImage"
}
},
{
"id": 10,
"type": "VAEDecode",
"pos": [
2870,
630
],
"size": {
"0": 210,
"1": 46
},
"flags": {
"collapsed": false
},
"order": 11,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 8
},
{
"name": "vae",
"type": "VAE",
"link": 9
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
24,
25
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
},
{
"id": 23,
"type": "AppInfo",
"pos": [
3363.0014990624995,
551.2261418945309
],
"size": {
"0": 400,
"1": 344
},
"flags": {},
"order": 13,
"mode": 0,
"inputs": [
{
"name": "LOGO",
"type": "IMAGE",
"link": 25
}
],
"properties": {
"Node name for S&R": "AppInfo"
},
"widgets_values": [
"Prompt-weight-test",
"21\n22\n18",
"11\n16",
"",
1,
"",
"https://",
"",
"enable",
1
]
}
],
"links": [
@@ -651,22 +649,6 @@
1,
"VAE"
],
[
12,
10,
0,
12,
0,
"IMAGE"
],
[
13,
12,
0,
11,
0,
"IMAGE"
],
[
16,
15,
@@ -714,6 +696,22 @@
13,
1,
"STRING"
],
[
24,
10,
0,
11,
0,
"IMAGE"
],
[
25,
10,
0,
23,
0,
"IMAGE"
]
],
"groups": [],
+166 -164
View File
@@ -1,6 +1,6 @@
{
"last_node_id": 25,
"last_link_id": 32,
"last_node_id": 26,
"last_link_id": 34,
"nodes": [
{
"id": 5,
@@ -41,48 +41,6 @@
"text, watermark"
]
},
{
"id": 6,
"type": "VAEDecode",
"pos": [
1867,
-2378
],
"size": {
"0": 210,
"1": 46
},
"flags": {
"collapsed": false
},
"order": 10,
"mode": 0,
"inputs": [
{
"name": "samples",
"type": "LATENT",
"link": 7
},
{
"name": "vae",
"type": "VAE",
"link": 8
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": [
9
],
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
},
{
"id": 15,
"type": "EnhanceImage",
@@ -101,7 +59,7 @@
{
"name": "image",
"type": "IMAGE",
"link": 25
"link": 34
}
],
"outputs": [
@@ -122,57 +80,6 @@
1.1
]
},
{
"id": 21,
"type": "EmptyLatentImage",
"pos": [
998,
-2674
],
"size": [
315,
106
],
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{
"name": "width",
"type": "INT",
"link": 30,
"widget": {
"name": "width"
}
},
{
"name": "height",
"type": "INT",
"link": 32,
"widget": {
"name": "height"
}
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
26
],
"shape": 3
}
],
"properties": {
"Node name for S&R": "EmptyLatentImage"
},
"widgets_values": [
512,
512,
1
]
},
{
"id": 24,
"type": "LimitNumber",
@@ -262,7 +169,7 @@
],
"size": {
"0": 315,
"1": 58
"1": 130
},
"flags": {},
"order": 0,
@@ -283,7 +190,10 @@
"Node name for S&R": "IntNumber"
},
"widgets_values": [
512
1,
0,
1,
1
]
},
{
@@ -295,7 +205,7 @@
],
"size": {
"0": 315,
"1": 58
"1": 130
},
"flags": {},
"order": 1,
@@ -316,7 +226,10 @@
"Node name for S&R": "IntNumber"
},
"widgets_values": [
512
1,
0,
1,
1
]
},
{
@@ -428,7 +341,7 @@
"Node name for S&R": "KSampler"
},
"widgets_values": [
730250870715434,
52336089190516,
"randomize",
15,
6.9,
@@ -437,39 +350,6 @@
0.59
]
},
{
"id": 14,
"type": "FloatSlider",
"pos": [
1007,
-2819
],
"size": {
"0": 315,
"1": 58
},
"flags": {},
"order": 3,
"mode": 0,
"outputs": [
{
"name": "FLOAT",
"type": "FLOAT",
"links": [
16
],
"shape": 3,
"slot_index": 0
}
],
"title": "denoise",
"properties": {
"Node name for S&R": "FloatSlider"
},
"widgets_values": [
1
]
},
{
"id": 16,
"type": "PreviewImage",
@@ -535,24 +415,31 @@
]
},
{
"id": 7,
"type": "AppInfo",
"id": 6,
"type": "VAEDecode",
"pos": [
2134,
-2528
1867,
-2378
],
"size": {
"0": 408.4201965332031,
"1": 406.9195861816406
"0": 210,
"1": 46
},
"flags": {},
"order": 11,
"flags": {
"collapsed": false
},
"order": 10,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 9
"name": "samples",
"type": "LATENT",
"link": 7
},
{
"name": "vae",
"type": "VAE",
"link": 8
}
],
"outputs": [
@@ -560,12 +447,37 @@
"name": "IMAGE",
"type": "IMAGE",
"links": [
25
33,
34
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "VAEDecode"
}
},
{
"id": 26,
"type": "AppInfo",
"pos": [
2134,
-2264
],
"size": {
"0": 400,
"1": 344.00006103515625
},
"flags": {},
"order": 11,
"mode": 0,
"inputs": [
{
"name": "LOGO",
"type": "IMAGE",
"link": 33
}
],
"properties": {
"Node name for S&R": "AppInfo"
},
@@ -576,7 +488,97 @@
"演示基本的文生图流程",
1,
"#comfyui-mixlab-nodes# ",
null
"https://",
"",
"enable",
1
]
},
{
"id": 14,
"type": "FloatSlider",
"pos": [
1007,
-2860
],
"size": {
"0": 315,
"1": 130
},
"flags": {},
"order": 3,
"mode": 0,
"outputs": [
{
"name": "FLOAT",
"type": "FLOAT",
"links": [
16
],
"shape": 3,
"slot_index": 0
}
],
"title": "denoise",
"properties": {
"Node name for S&R": "FloatSlider"
},
"widgets_values": [
1,
0,
1,
0.001
]
},
{
"id": 21,
"type": "EmptyLatentImage",
"pos": [
1004,
-2633
],
"size": {
"0": 315,
"1": 106
},
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{
"name": "width",
"type": "INT",
"link": 30,
"widget": {
"name": "width"
}
},
{
"name": "height",
"type": "INT",
"link": 32,
"widget": {
"name": "height"
}
}
],
"outputs": [
{
"name": "LATENT",
"type": "LATENT",
"links": [
26
],
"shape": 3
}
],
"properties": {
"Node name for S&R": "EmptyLatentImage"
},
"widgets_values": [
512,
512,
1
]
}
],
@@ -637,14 +639,6 @@
1,
"VAE"
],
[
9,
6,
0,
7,
0,
"IMAGE"
],
[
16,
14,
@@ -661,14 +655,6 @@
0,
"IMAGE"
],
[
25,
7,
0,
15,
0,
"IMAGE"
],
[
26,
21,
@@ -708,6 +694,22 @@
21,
1,
"INT"
],
[
33,
6,
0,
26,
0,
"IMAGE"
],
[
34,
6,
0,
15,
0,
"IMAGE"
]
],
"groups": [],
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 2.5 MiB

After

Width:  |  Height:  |  Size: 2.5 MiB

+281 -235
View File
@@ -1,6 +1,6 @@
{
"last_node_id": 133,
"last_link_id": 289,
"last_node_id": 134,
"last_link_id": 296,
"nodes": [
{
"id": 42,
@@ -178,7 +178,7 @@
"Node name for S&R": "KSampler"
},
"widgets_values": [
487152733962296,
981322491518329,
"randomize",
13,
8,
@@ -284,7 +284,7 @@
{
"name": "width",
"type": "INT",
"link": 91,
"link": 291,
"widget": {
"name": "width"
}
@@ -292,7 +292,7 @@
{
"name": "height",
"type": "INT",
"link": 92,
"link": 292,
"widget": {
"name": "height"
}
@@ -554,7 +554,7 @@
"Node name for S&R": "KSampler"
},
"widgets_values": [
1031291986804327,
836429510062488,
"randomize",
13,
8,
@@ -824,7 +824,7 @@
"Node name for S&R": "KSampler"
},
"widgets_values": [
715240655341851,
24900528323969,
"randomize",
12,
8,
@@ -875,67 +875,6 @@
"Node name for S&R": "VAEDecode"
}
},
{
"id": 46,
"type": "NewLayer",
"pos": [
1012.7987748686522,
1330
],
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 32,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 78
},
{
"name": "mask",
"type": "MASK",
"link": 79
},
{
"name": "layers",
"type": "LAYER",
"link": 227,
"slot_index": 2
},
{
"name": "canvas",
"type": "IMAGE",
"link": null
}
],
"outputs": [
{
"name": "layers",
"type": "LAYER",
"links": [
228
],
"shape": 6,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "NewLayer"
},
"widgets_values": [
179,
134,
156,
146,
4,
"width",
""
]
},
{
"id": 65,
"type": "NewLayer",
@@ -1221,42 +1160,6 @@
""
]
},
{
"id": 28,
"type": "Reroute",
"pos": [
616,
1515
],
"size": [
75,
26
],
"flags": {},
"order": 13,
"mode": 0,
"inputs": [
{
"name": "",
"type": "*",
"link": 48
}
],
"outputs": [
{
"name": "",
"type": "IMAGE",
"links": [
90
],
"slot_index": 0
}
],
"properties": {
"showOutputText": false,
"horizontal": false
}
},
{
"id": 96,
"type": "Reroute",
@@ -1294,55 +1197,6 @@
"horizontal": false
}
},
{
"id": 49,
"type": "GetImageSize",
"pos": [
999,
1189
],
"size": {
"0": 210,
"1": 46
},
"flags": {},
"order": 24,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 90
}
],
"outputs": [
{
"name": "width",
"type": "INT",
"links": [
91,
238,
248
],
"shape": 3,
"slot_index": 0
},
{
"name": "height",
"type": "INT",
"links": [
92,
239,
249
],
"shape": 3,
"slot_index": 1
}
],
"properties": {
"Node name for S&R": "GetImageSize"
}
},
{
"id": 118,
"type": "NewLayer",
@@ -1376,7 +1230,7 @@
{
"name": "width",
"type": "INT",
"link": 248,
"link": 295,
"widget": {
"name": "width"
}
@@ -1384,7 +1238,7 @@
{
"name": "height",
"type": "INT",
"link": 249,
"link": 296,
"widget": {
"name": "height"
}
@@ -1572,7 +1426,7 @@
{
"name": "width",
"type": "INT",
"link": 238,
"link": 293,
"widget": {
"name": "width"
}
@@ -1580,7 +1434,7 @@
{
"name": "height",
"type": "INT",
"link": 239,
"link": 294,
"widget": {
"name": "height"
}
@@ -1955,7 +1809,7 @@
"Node name for S&R": "KSampler"
},
"widgets_values": [
397643785545040,
371831550273153,
"randomize",
13,
8,
@@ -2164,7 +2018,7 @@
"Node name for S&R": "KSampler"
},
"widgets_values": [
590319162197911,
996820396025175,
"randomize",
13,
8,
@@ -2310,7 +2164,7 @@
"Node name for S&R": "KSampler"
},
"widgets_values": [
457217754867837,
763274893004397,
"randomize",
13,
8,
@@ -2780,7 +2634,7 @@
],
"size": {
"0": 315,
"1": 234
"1": 240
},
"flags": {},
"order": 3,
@@ -2814,7 +2668,8 @@
100,
13,
null,
true
true,
false
]
},
{
@@ -2986,10 +2841,10 @@
100,
706
],
"size": [
315,
234
],
"size": {
"0": 315,
"1": 234
},
"flags": {},
"order": 15,
"mode": 0,
@@ -3034,7 +2889,8 @@
100,
13,
null,
true
true,
false
]
},
{
@@ -3044,10 +2900,10 @@
100,
1330
],
"size": [
315,
234
],
"size": {
"0": 315,
"1": 234
},
"flags": {},
"order": 16,
"mode": 0,
@@ -3091,7 +2947,8 @@
100,
13,
null,
true
true,
false
]
},
{
@@ -3101,10 +2958,10 @@
100,
1922
],
"size": [
315,
234
],
"size": {
"0": 315,
"1": 234
},
"flags": {},
"order": 17,
"mode": 0,
@@ -3147,7 +3004,8 @@
100,
13,
null,
true
true,
false
]
},
{
@@ -3159,7 +3017,7 @@
],
"size": {
"0": 315,
"1": 70
"1": 174
},
"flags": {},
"order": 6,
@@ -3175,13 +3033,37 @@
],
"shape": 3,
"slot_index": 0
},
{
"name": "r",
"type": "INT",
"links": null,
"shape": 3
},
{
"name": "g",
"type": "INT",
"links": null,
"shape": 3
},
{
"name": "b",
"type": "INT",
"links": null,
"shape": 3
},
{
"name": "a",
"type": "FLOAT",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "Color"
},
"widgets_values": [
null,
"#000000",
null
]
},
@@ -3221,6 +3103,12 @@
],
"shape": 3,
"slot_index": 0
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3
}
],
"properties": {
@@ -3263,6 +3151,12 @@
],
"shape": 3,
"slot_index": 0
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3
}
],
"properties": {
@@ -3306,6 +3200,12 @@
],
"shape": 3,
"slot_index": 0
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3
}
],
"properties": {
@@ -3334,6 +3234,152 @@
],
"color": "#432",
"bgcolor": "#653"
},
{
"id": 134,
"type": "GetImageSize_",
"pos": [
734,
1628
],
"size": {
"0": 210,
"1": 46
},
"flags": {},
"order": 24,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 290
}
],
"outputs": [
{
"name": "width",
"type": "INT",
"links": [
291,
293,
295
],
"shape": 3,
"slot_index": 0
},
{
"name": "height",
"type": "INT",
"links": [
292,
294,
296
],
"shape": 3,
"slot_index": 1
}
],
"properties": {
"Node name for S&R": "GetImageSize_"
}
},
{
"id": 28,
"type": "Reroute",
"pos": [
597,
1627
],
"size": [
75,
26
],
"flags": {},
"order": 13,
"mode": 0,
"inputs": [
{
"name": "",
"type": "*",
"link": 48
}
],
"outputs": [
{
"name": "",
"type": "IMAGE",
"links": [
290
],
"slot_index": 0
}
],
"properties": {
"showOutputText": false,
"horizontal": false
}
},
{
"id": 46,
"type": "NewLayer",
"pos": [
1056,
1247
],
"size": {
"0": 315,
"1": 262
},
"flags": {},
"order": 32,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": 78
},
{
"name": "mask",
"type": "MASK",
"link": 79
},
{
"name": "layers",
"type": "LAYER",
"link": 227,
"slot_index": 2
},
{
"name": "canvas",
"type": "IMAGE",
"link": null
}
],
"outputs": [
{
"name": "layers",
"type": "LAYER",
"links": [
228
],
"shape": 6,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "NewLayer"
},
"widgets_values": [
179,
134,
156,
146,
4,
"width",
""
]
}
],
"links": [
@@ -3433,30 +3479,6 @@
1,
"MASK"
],
[
90,
28,
0,
49,
0,
"IMAGE"
],
[
91,
49,
0,
50,
0,
"INT"
],
[
92,
49,
1,
50,
1,
"INT"
],
[
111,
63,
@@ -4001,22 +4023,6 @@
0,
"IMAGE"
],
[
238,
49,
0,
116,
3,
"INT"
],
[
239,
49,
1,
116,
4,
"INT"
],
[
247,
116,
@@ -4025,22 +4031,6 @@
2,
"LAYER"
],
[
248,
49,
0,
118,
3,
"INT"
],
[
249,
49,
1,
118,
4,
"INT"
],
[
251,
119,
@@ -4312,6 +4302,62 @@
119,
0,
"IMAGE"
],
[
290,
28,
0,
134,
0,
"IMAGE"
],
[
291,
134,
0,
50,
0,
"INT"
],
[
292,
134,
1,
50,
1,
"INT"
],
[
293,
134,
0,
116,
3,
"INT"
],
[
294,
134,
1,
116,
4,
"INT"
],
[
295,
134,
0,
118,
3,
"INT"
],
[
296,
134,
1,
118,
4,
"INT"
]
],
"groups": [],
File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 1.2 MiB

After

Width:  |  Height:  |  Size: 589 B