Compare commits

..
Author SHA1 Message Date
shadowcz007 5b8cab920c 增加示例 2023-12-24 10:56:56 +08:00
shadowcz007 8d3d327335 Update Utils.py 2023-12-24 10:53:05 +08:00
shadowcz007 32574050c4 增加从语音识别发送到chatgpt的方法 2023-12-24 10:44:25 +08:00
shadowcz007 8d45a90d9b Update Utils.py 2023-12-24 09:31:42 +08:00
gold3bear f66862a422 update DynamicDelayProcessor 2023-12-24 00:06:40 +08:00
gold3bear ebf6395de2 delay by text processor 2023-12-23 23:16:56 +08:00
shadow 99def24dd8 Merge pull request #73 from shadowcz007/v0.5-GamePal
支持换行的textimage
2023-12-23 14:13:31 +08:00
shadowcz007 f5b210d142 支持换行的textimage 2023-12-23 14:13:04 +08:00
shadow a137a23b48 Merge pull request #72 from shadowcz007/v0.5-GamePal
TextToNumber&audio input control
2023-12-23 13:16:08 +08:00
shadowcz007 6bbf06d9e9 TextToNumber&audio input control 2023-12-23 13:15:47 +08:00
shadowcz007 0b614b40cf Merge branch 'main' of https://github.com/shadowcz007/comfyui-mixlab-nodes 2023-12-23 10:49:27 +08:00
shadowcz007 27673561bd 使用comfyui的ui来控制刷新率 2023-12-23 10:49:24 +08:00
shadow 6e2070410d Merge pull request #70 from shadowcz007/v0.3.2-3DImage
Delete layers-test-workflow.json
2023-12-22 20:36:39 +08:00
shadowcz007 7ccf21f74f Delete layers-test-workflow.json 2023-12-22 20:36:04 +08:00
shadow 3b2710f285 Merge pull request #69 from shadowcz007/v0.3.2-3DImage
v0.4.2
2023-12-22 20:28:33 +08:00
shadowcz007 c4b277235b 1 2023-12-22 20:27:53 +08:00
shadowcz007 a55318add1 v0.4.2 2023-12-22 20:24:19 +08:00
shadowcz007 b57123a4fe Update 3D-workflow.json 2023-12-22 20:21:57 +08:00
shadowcz007 04dcc00670 增加可视化选区 2023-12-22 20:20:03 +08:00
shadowcz007 746a02b49f test- 2023-12-22 12:16:39 +08:00
shadowcz007 bdbe3db2a9 Update Vae.py 2023-12-21 10:39:32 +08:00
shadowcz007 27ae99ad86 Update __init__.py 2023-12-21 10:26:16 +08:00
shadowcz007 38add89547 update style 2023-12-21 10:24:01 +08:00
shadowcz007 a9612fbb2f 增加一个resize节点 2023-12-20 16:12:09 +08:00
shadowcz007 429cc29b5b test 2023-12-20 15:08:13 +08:00
shadowcz007 8eca94e405 test 2023-12-20 14:32:57 +08:00
shadowcz007 ad71daafb6 Merge branch 'v0.3.2-3DImage' of https://github.com/shadowcz007/comfyui-mixlab-nodes into v0.3.2-3DImage 2023-12-20 12:18:41 +08:00
shadowcz007 c936d83688 1 2023-12-20 12:18:38 +08:00
shadow c6684d680f Merge pull request #66 from shadowcz007/main
0.4.1
2023-12-20 10:50:26 +08:00
shadowcz007 897f259a2a Merge branch 'v0.3.2-3DImage' of https://github.com/shadowcz007/comfyui-mixlab-nodes into v0.3.2-3DImage 2023-12-20 00:10:05 +08:00
shadowcz007 f3302c1b3a update 2023-12-20 00:08:05 +08:00
shadow 573feeaaab Merge pull request #64 from shadowcz007/main
1
2023-12-20 00:05:39 +08:00
19 changed files with 2739 additions and 2179 deletions
+15 -7
View File
@@ -318,13 +318,13 @@ PromptServer.add_routes=new_add_routes
# 导入节点
from .nodes.PromptNode import RandomPrompt
from .nodes.ImageNode import TransparentImage,LoadImagesFromPath,TextImage,SvgImage,Image3D,EmptyLayer,ShowLayer,NewLayer,MergeLayers,AreaToMask,SmoothMask,FeatheredMask,SplitLongMask,ImageCropByAlpha,EnhanceImage,FaceToMask
from .nodes.ImageNode import TransparentImage,LoadImagesFromPath,ResizeImage,TextImage,SvgImage,Image3D,EmptyLayer,ShowLayer,NewLayer,MergeLayers,AreaToMask,SmoothMask,FeatheredMask,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 SpeechRecognition,SpeechSynthesis
from .nodes.Utils import ColorInput,FontInput
from .nodes.Audio import GamePal,SpeechRecognition,SpeechSynthesis
from .nodes.Utils import ColorInput,FontInput,TextToNumber,DynamicDelayProcessor
# 要导出的所有节点及其名称的字典
@@ -332,12 +332,12 @@ from .nodes.Utils import ColorInput,FontInput
NODE_CLASS_MAPPINGS = {
"RandomPrompt":RandomPrompt,
"TransparentImage":TransparentImage,
"ResizeImageMixlab":ResizeImage,
"LoadImagesFromPath":LoadImagesFromPath,
"TextImage":TextImage,
"EnhanceImage":EnhanceImage,
"SvgImage":SvgImage,
"3DImage":Image3D,
"EmptyLayer":EmptyLayer,
"3DImage":Image3D,
"ShowLayer":ShowLayer,
"NewLayer":NewLayer,
"MergeLayers":MergeLayers,
@@ -359,11 +359,15 @@ NODE_CLASS_MAPPINGS = {
"SpeechRecognition":SpeechRecognition,
"SpeechSynthesis":SpeechSynthesis,
"Color":ColorInput,
"Font":FontInput
"Font":FontInput,
"TextToNumber":TextToNumber,
"DynamicDelayProcessor":DynamicDelayProcessor
# "GamePal":GamePal
}
# 一个包含节点友好/可读的标题的字典
NODE_DISPLAY_NAME_MAPPINGS = {
"ResizeImageMixlab":"ResizeImage",
"RandomPrompt": "Random Prompt ♾️Mixlab",
"SplitLongMask":"Splitting a long image into sections",
"VAELoaderConsistencyDecoder":"Consistency Decoder Loader",
@@ -374,7 +378,11 @@ NODE_DISPLAY_NAME_MAPPINGS = {
"ShowTextForGPT":"ShowTextForGPT ♾️Mixlab",
"MergeLayers":"MergeLayers ♾️Mixlab",
"SpeechSynthesis":"SpeechSynthesis ♾️Mixlab",
"SpeechRecognition":"SpeechRecognition ♾️Mixlab"
"SpeechRecognition":"SpeechRecognition ♾️Mixlab",
"3DImage":"3DImage ♾️Mixlab",
"DynamicDelayProcessor":"DynamicDelayByText ♾️Mixlab"
# "GamePal":"GamePal ♾️Mixlab"
}
# web ui的节点功能
+56 -2
View File
@@ -7,6 +7,16 @@ class SpeechRecognition:
def INPUT_TYPES(s):
return {"required": {
"upload":("AUDIOINPUTMIX",), },
"optional":{
"start_by":("INT", {
"default": 0,
"min": 0, #Minimum value
"max": 2048, #Maximum value
"step": 1, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
}),
}
}
RETURN_TYPES = ("STRING",)
@@ -19,8 +29,8 @@ class SpeechRecognition:
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
def run(self,upload):
return (upload,)
def run(self,upload,start_by):
return {"ui": {"start_by": [start_by]}, "result": (upload,)}
class SpeechSynthesis:
@@ -44,3 +54,47 @@ class SpeechSynthesis:
# print(session_history)
return {"ui": {"text": text}, "result": (text,)}
#
class GamePal:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"input_text": ("STRING",{"multiline": True,"default": ""}),
},
"optional": {
"input_num": ("INT",{
"default":100,
"min": -1, #Minimum value
"max": 0xffffffffffffffff, #Maximum value
"step": 1, #Slider's step
"display": "slider" # Cosmetic only: display as "number" or "slider"
}),
"python_code": ("STRING",{"multiline": True,"default": "result= 1 if 'Mixlab' in input_text else 0"}),
}
}
INPUT_IS_LIST = False
RETURN_TYPES = ("INT",)
FUNCTION = "run"
OUTPUT_NODE = True
OUTPUT_IS_LIST = (False,)
CATEGORY = "♾️Mixlab/audio"
def run(self, input_text,input_num,python_code):
exec(python_code)
res=None
try:
# 可能会引发异常的代码
res=result
except:
# 处理异常的代码
print('')
print(res)
# print(session_history)
return {"ui": {"text": [input_text],"num":[input_num]}, "result": (res,)}
+211 -36
View File
@@ -376,34 +376,119 @@ def merge_images(bg_image, layer_image, mask, x, y, width, height, scale_option)
return bg_image
def resize_image(layer_image,scale_option,width,height):
layer_image = layer_image.convert("RGB")
if scale_option == "height":
# 按照高度比例缩放
original_width, original_height = layer_image.size
scale = height / original_height
new_width = int(original_width * scale)
layer_image = layer_image.resize((new_width, height))
elif scale_option == "width":
# 按照宽度比例缩放
original_width, original_height = layer_image.size
scale = width / original_width
new_height = int(original_height * scale)
layer_image = layer_image.resize((width, new_height))
elif scale_option == "overall":
# 整体缩放
layer_image = layer_image.resize((width, height))
return layer_image
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)
# Calculate image size based on the number of characters and orientation
# 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)
# # Calculate image size based on the number of characters and orientation
# if vertical:
# width = font_size + 100
# height = font_size * len(text_list) + (len(text_list) - 1) * spacing + 100
# else:
# width = font_size * len(text_list) + (len(text_list) - 1) * spacing + 100
# height = font_size + 100
# # Create a blank image
# image = Image.new('RGBA', (width, height), (255, 255, 255,0))
# draw = ImageDraw.Draw(image)
# # Draw text
# if vertical:
# for i, char in enumerate(text_list):
# char_position = (50, 50 + i * font_size)
# draw.text(char_position, char, font=font, fill=text_color)
# else:
# for i, char in enumerate(text_list):
# char_position = (50 + i * (font_size + spacing), 50)
# draw.text(char_position, char, font=font, fill=text_color)
# # Save the image
# # image.save(output_image_path)
# # 分离alpha通道
# alpha_channel = image.split()[3]
# # 创建一个只有alpha通道的新图像
# alpha_image = Image.new('L', image.size)
# alpha_image.putdata(alpha_channel.getdata())
# image=image.convert('RGB')
# return (image,alpha_image)
def generate_text_image(text, font_path, font_size, text_color, vertical=True, spacing=0):
# Split text into lines based on line breaks
lines = text.split("\n")
# 1. Determine layout direction
if vertical:
width = font_size + 100
height = font_size * len(text_list) + (len(text_list) - 1) * spacing + 100
layout = "vertical"
else:
width = font_size * len(text_list) + (len(text_list) - 1) * spacing + 100
height = font_size + 100
layout = "horizontal"
# Create a blank image
# 2. Calculate absolute coordinates for each character
char_coordinates = []
if layout == "vertical":
x = 0
y = 0
for i in range(len(lines)):
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:
char_coordinates.append((x, y))
x += font_size + spacing
y += font_size + spacing
x = 0
# 3. Calculate image width and height
if layout == "vertical":
width = (len(lines) * (font_size + spacing)) - spacing
height = (len(max(lines, key=len)) * (font_size + spacing)) - spacing
else:
width = (len(max(lines, key=len)) * (font_size + spacing)) - spacing
height = (len(lines) * (font_size + spacing)) - spacing
# 4. Draw each character on the image
image = Image.new('RGBA', (width, height), (255, 255, 255,0))
draw = ImageDraw.Draw(image)
font = ImageFont.truetype(font_path, font_size)
index=0
for i, line in enumerate(lines):
for j, char in enumerate(line):
x, y = char_coordinates[index]
draw.text((x, y), char, font=font, fill=text_color)
index+=1
# Draw text
if vertical:
for i, char in enumerate(text_list):
char_position = (50, 50 + i * font_size)
draw.text(char_position, char, font=font, fill=text_color)
else:
for i, char in enumerate(text_list):
char_position = (50 + i * (font_size + spacing), 50)
draw.text(char_position, char, font=font, fill=text_color)
# Save the image
# image.save(output_image_path)
# 分离alpha通道
@@ -417,7 +502,6 @@ def generate_text_image(text_list, font_path, font_size, text_color, vertical=Tr
return (image,alpha_image)
def base64_to_image(base64_string):
# 去除前缀
prefix, base64_data = base64_string.split(",", 1)
@@ -434,6 +518,32 @@ def base64_to_image(base64_string):
return image
def create_temp_file(image):
output_dir = folder_paths.get_temp_directory()
(
full_output_folder,
filename,
counter,
subfolder,
_,
) = folder_paths.get_save_image_path('material', output_dir)
image=tensor2pil(image)
image_file = f"{filename}_{counter:05}.png"
image_path=os.path.join(full_output_folder, image_file)
image.save(image_path,compress_level=4)
return [{
"filename": image_file,
"subfolder": subfolder,
"type": "temp"
}]
class SmoothMask:
@classmethod
@@ -659,7 +769,7 @@ class TransparentImage:
# ui.images 节点里显示图片,和 传参,image_path自定义的数据,需要写节点的自定义ui
# result 里输出给下个节点的数据
print('TransparentImage',len(images_rgb))
# print('TransparentImage',len(images_rgb))
return {"ui":{"images": ui_images,"image_paths":image_paths},"result": (image_paths,images_rgb,images_rgba)}
@@ -822,12 +932,14 @@ class ImageCropByAlpha:
class TextImage:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"text": ("STRING",{"multiline": False,"default": "龍馬精神迎新歲"}),
"text": ("STRING",{"multiline": True,"default": "龍馬精神迎新歲"}),
"font_path": ("STRING",{"multiline": False,"default": FONT_PATH}),
"font_size": ("INT",{
"default":100,
@@ -862,7 +974,7 @@ class TextImage:
text_list=list(text)
img,mask=generate_text_image(text_list,font_path,font_size,text_color,vertical,spacing)
img,mask=generate_text_image(text,font_path,font_size,text_color,vertical,spacing)
img=pil2tensor(img)
mask=pil2tensor(mask)
@@ -906,25 +1018,31 @@ class Image3D:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"upload":("THREED",), },
"upload":("THREED",),},
"optional":{
"material": ("IMAGE",),
}
}
RETURN_TYPES = ("IMAGE","MASK","IMAGE",)
RETURN_NAMES = ("IMAGE","MASK","BG_IMAGE",)
RETURN_TYPES = ("IMAGE","MASK","IMAGE","IMAGE",)
RETURN_NAMES = ("IMAGE","MASK","BG_IMAGE","MATERIAL",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/image"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,False,False,)
OUTPUT_IS_LIST = (False,False,False,False,)
OUTPUT_NODE = True
def run(self,upload):
def run(self,upload,material=None):
# print('material',material)
# print(upload['image'])
image = base64_to_image(upload['image'])
mat=base64_to_image(upload['material'])
mask = image.split()[3]
image=image.convert('RGB')
mat=mat.convert('RGB')
mask=mask.convert('L')
bg_image=None
@@ -936,8 +1054,14 @@ class Image3D:
mask=pil2tensor(mask)
image=pil2tensor(image)
mat=pil2tensor(mat)
m=[]
if not material is None:
m=create_temp_file(material[0])
return (image,mask,bg_image,)
return {"ui":{"material": m},"result": (image,mask,bg_image,mat,)}
@@ -1104,6 +1228,7 @@ class NewLayer:
"optional":{
"mask": ("MASK",{"default": None}),
"layers": ("LAYER",{"default": None}),
"canvas": ("IMAGE",{"default": None}),
}
}
@@ -1117,16 +1242,16 @@ class NewLayer:
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (True,)
def run(self,x,y,width,height,z_index,scale_option,image,mask,layers):
def run(self,x,y,width,height,z_index,scale_option,image,mask=None,layers=None,canvas=None):
# print(x,y,width,height,z_index,image,mask)
if mask==None:
im=tensor2pil(image)
im=tensor2pil(image[0])
mask=im.convert('L')
mask=pil2tensor(mask)
else:
mask=mask[0]
layer_n=[{
"x":x[0],
"y":y[0],
@@ -1143,7 +1268,6 @@ class NewLayer:
return (layer_n,)
class ShowLayer:
@classmethod
def INPUT_TYPES(s):
@@ -1291,4 +1415,55 @@ class MergeLayers:
# print(mask,mask.shape)
mask = mask[:, :, :, channels.index("green")]
return (bg_image,mask,)
return (bg_image,mask,)
class ResizeImage:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"image": ("IMAGE",),
"width": ("INT",{
"default": 512,
"min": 1, #Minimum value
"max": 8192, #Maximum value
"step": 1, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
}),
"height": ("INT",{
"default": 512,
"min": 1, #Minimum value
"max": 8192, #Maximum value
"step": 1, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
}),
"scale_option": (["width","height",'overall'],),
},
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/image"
INPUT_IS_LIST = True
OUTPUT_IS_LIST = (False,)
def run(self,image,width,height,scale_option):
w=width[0]
h=height[0]
scale_option=scale_option[0]
im=image[0]
im=tensor2pil(im)
im=resize_image(im,scale_option,w,h)
im=im.convert('RGB')
im=pil2tensor(im)
return (im,)
+5 -3
View File
@@ -79,11 +79,13 @@ class ScreenShareNode:
def INPUT_TYPES(s):
return { "required":{
"image_base64": ("CHEESE",),
"refresh_rate": ("INT", {"default": 500, "min": 0,"step": 50, "max": 0xffffffffffffffff}),
},
"optional":{
"prompt": ("PROMPT",),
"slide": ("SLIDE",),
"seed": ("SEED",),
# "seed": ("INT", {"default": 1, "min": 0, "max": 0xffffffffffffffff}),
} }
@@ -97,11 +99,11 @@ class ScreenShareNode:
OUTPUT_IS_LIST = (False,False,False,False)
# 运行的函数
def run(self,image_base64,prompt,slide,seed):
def run(self,image_base64,refresh_rate ,prompt,slide,seed):
im,mask=base64_save(image_base64)
# print('##########prompt',prompt)
return (im,prompt,slide,seed)
return {"ui":{"refresh_rate": [refresh_rate]},"result": (im,prompt,slide,seed,)}
class FloatingVideo:
@classmethod
+131 -2
View File
@@ -1,5 +1,5 @@
import os
import re,random
# FONT_PATH= os.path.abspath(os.path.join(os.path.dirname(__file__),'../assets/王汉宗颜楷体繁.ttf'))
import matplotlib.font_manager as fm
@@ -80,4 +80,133 @@ class FontInput:
def run(self,font):
return (font_files[font],)
return (font_files[font],)
class TextToNumber:
@classmethod
def INPUT_TYPES(s):
return {"required": {
"text": ("STRING",{"multiline": False,"default": "1"}),
"random_number": (["enable", "disable"],),
"number":("INT", {
"default": 0,
"min": 0, #Minimum value
"max": 10000000000, #Maximum value
"step": 1, #Slider's step
"display": "number" # Cosmetic only: display as "number" or "slider"
}),
},
}
RETURN_TYPES = ("INT",)
# RETURN_NAMES = ("WIDTH","HEIGHT","X","Y",)
FUNCTION = "run"
CATEGORY = "♾️Mixlab/utils"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,)
def run(self,text,random_number,number):
numbers = re.findall(r'\d+', text)
result=0
for n in numbers:
result = int(n)
# print(result)
if random_number=='enable' and result>0:
result= random.randint(1, 10000000000)
return {"ui": {"text": [text],"num":[result]}, "result": (result,)}
# 接收一个值,然后根据字符串或数值长度计算延迟时间,用户可以自定义延迟"字/s",延迟之后将转化
import comfy.samplers
import folder_paths
# import time
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("*")
import time
class DynamicDelayProcessor:
@classmethod
def INPUT_TYPES(cls):
print("print INPUT_TYPES",cls)
return {
"required":{
"delay_seconds":("INT",{
"default":1,
"min": 0,
"max": 1000000,
}),
},
"optional":{
"any_input":(any_type,),
"delay_by_text":("STRING",{"multiline":True,}),
"words_per_seconds":("FLOAT",{ "default":1.50,"min": 0.0,"max": 1000.00,"display":"Chars per second?"}),
"replace_output": (["disable","enable"],),
"replace_value":("INT",{ "default":-1,"min": 0,"max": 1000000,"display":"Replacement value"})
}
}
@classmethod
def calculate_words_length(cls,text):
chinese_char_pattern = re.compile(r'[\u4e00-\u9fff]')
english_word_pattern = re.compile(r'\b[a-zA-Z]+\b')
number_pattern = re.compile(r'\b[0-9]+\b')
words_length = 0
for segment in text.split():
if chinese_char_pattern.search(segment):
# 中文字符,每个字符计为 1
words_length += len(segment)
elif number_pattern.match(segment):
# 数字,每个字符计为 1
words_length += len(segment)
elif english_word_pattern.match(segment):
# 英文单词,整个单词计为 1
words_length += 1
return words_length
FUNCTION = "run"
RETURN_TYPES = (any_type,)
RETURN_NAMES = ('output',)
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 )
# 获取开始时间戳
start_time = time.time()
# 计算延迟时间
delay_time = delay_seconds
if delay_by_text and isinstance(delay_by_text, str) and words_per_seconds > 0:
words_length = self.calculate_words_length(delay_by_text)
print(f"Delay text: {delay_by_text}, Length: {words_length}")
delay_time += words_length / words_per_seconds
# 延迟执行
print(f"延迟执行: {delay_time}")
time.sleep(delay_time)
# 获取结束时间戳并计算间隔
end_time = time.time()
elapsed_time = end_time - start_time
print(f"实际延迟时间: {elapsed_time} 秒")
# 根据 replace_output 决定输出值
return (max(0, replace_value),) if replace_output == "enable" else (any_input,)
+2 -2
View File
@@ -145,7 +145,7 @@ class VAELoader:
RETURN_TYPES = ("VAE",)
FUNCTION = "load_vae"
CATEGORY = "♾️Mixlab/ConsistencyDecoder"
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/ConsistencyDecoder"
CATEGORY = "♾️Mixlab/_test"
def decode(self, vae, samples):
image = vae.decode(samples["samples"].to("cuda:0"))
+608
View File
@@ -0,0 +1,608 @@
import { app } from '../../../scripts/app.js'
import { api } from '../../../scripts/api.js'
import { $el } from '../../../scripts/ui.js'
const getLocalData = key => {
let data = {}
try {
data = JSON.parse(localStorage.getItem(key)) || {}
} catch (error) {
return {}
}
return data
}
function getContentTypeFromBase64 (base64Data) {
const regex = /^data:(.+);base64,/
const matches = base64Data.match(regex)
if (matches && matches.length >= 2) {
return matches[1]
}
return null
}
function base64ToBlobFromURL (base64URL, contentType) {
return fetch(base64URL).then(response => response.blob())
}
const setLocalDataOfWin = (key, value) => {
localStorage.setItem(key, JSON.stringify(value))
// window[key] = value
}
async function uploadImage (blob, fileType = '.svg', filename) {
// const blob = await (await fetch(src)).blob();
const body = new FormData()
body.append(
'image',
new File([blob], (filename || new Date().getTime()) + fileType)
)
const resp = await api.fetchApi('/upload/image', {
method: 'POST',
body
})
// console.log(resp)
let data = await resp.json()
let { name, subfolder } = data
let src = api.apiURL(
`/view?filename=${encodeURIComponent(
name
)}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}`
)
return src
}
function createImage (url) {
let im = new Image()
return new Promise((res, rej) => {
im.onload = () => res(im)
im.src = url
})
}
const parseImage = url => {
return new Promise((res, rej) => {
fetch(url)
.then(response => response.blob())
.then(blob => {
const reader = new FileReader()
reader.onloadend = () => {
const base64data = reader.result
res(base64data)
// 在这里可以将base64数据用于进一步处理或显示图片
}
reader.readAsDataURL(blob)
})
.catch(error => {
console.log('发生错误:', error)
})
})
}
function get_position_style (ctx, widget_width, y, node_height) {
const MARGIN = 4 // the margin around the html element
/* Create a transform that deals with all the scrolling and zooming */
const elRect = ctx.canvas.getBoundingClientRect()
const transform = new DOMMatrix()
.scaleSelf(
elRect.width / ctx.canvas.width,
elRect.height / ctx.canvas.height
)
.multiplySelf(ctx.getTransform())
.translateSelf(MARGIN, MARGIN + y)
return {
transformOrigin: '0 0',
transform: transform,
left: `0`,
top: `0`,
cursor: 'pointer',
position: 'absolute',
maxWidth: `${widget_width - MARGIN * 2}px`,
// maxHeight: `${node_height - MARGIN * 2}px`, // we're assuming we have the whole height of the node
width: `${widget_width - MARGIN * 2}px`,
// height: `${node_height * 0.3 - MARGIN * 2}px`,
// background: '#EEEEEE',
display: 'flex',
flexDirection: 'column',
// alignItems: 'center',
justifyContent: 'space-around'
}
}
async function extractMaterial (
modelViewerVariants,
selectMaterial,
material_img
) {
// 材质
const materialsNames = []
for (
let index = 0;
index < modelViewerVariants.model.materials.length;
index++
) {
let m = modelViewerVariants.model.materials[index]
let thumbUrl
try {
thumbUrl =
await m.pbrMetallicRoughness.baseColorTexture.texture.source.createThumbnail(
1024,
1024
)
} catch (error) {}
if (thumbUrl)
materialsNames.push({
value: m.name,
text: `#${index} ${m.name}`,
index,
thumbUrl
})
}
selectMaterial.innerHTML = ''
material_img.innerHTML = ''
for (let index = 0; index < materialsNames.length; index++) {
const name = materialsNames[index]
const option = document.createElement('option')
option.value = name.thumbUrl
option.textContent = name.text
option.setAttribute('data-index', index)
selectMaterial.appendChild(option)
let img = new Image()
img.src = name.thumbUrl
// img.setAttribute('data-index',name.index)
img.style.width = '40px'
material_img.appendChild(img)
if (index == 0) {
material_img.setAttribute('src', name.thumbUrl)
}
}
}
async function changeMaterial (
modelViewerVariants,
targetMaterial,
newImageUrl
) {
const targetTexture = await modelViewerVariants.createTexture(newImageUrl)
// 用图片创建纹理
targetMaterial.pbrMetallicRoughness.baseColorTexture.setTexture(targetTexture)
}
app.registerExtension({
name: 'Mixlab.3D.3DImage',
async getCustomWidgets (app) {
return {
THREED (node, inputName, inputData, app) {
// console.log('##node', node, inputName, inputData)
const widget = {
type: inputData[0], // the type, CHEESE
name: inputName, // the name, slice
size: [128, 88], // a default size
draw (ctx, node, width, y) {},
computeSize (...args) {
return [128, 88] // a method to compute the current size of the widget
},
async serializeValue (nodeId, widgetIndex) {
let d = getLocalData('_mixlab_3d_image')
console.log('serializeValue', node)
if (d && d[node.id]) {
let { url, bg, material } = d[node.id]
let base64 = await parseImage(url)
let bg_base64 = await parseImage(bg)
let material_base64 = await parseImage(material)
return JSON.parse(
JSON.stringify({
image: base64,
bg_image: bg_base64,
material: material_base64
})
)
} else {
return {}
}
}
}
node.addCustomWidget(widget)
return widget
}
}
},
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == '3DImage') {
const orig_nodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = async function () {
orig_nodeCreated?.apply(this, arguments)
const uploadWidget = this.widgets.filter(w => w.name == 'upload')[0]
const widget = {
type: 'div',
name: 'upload-preview',
draw (ctx, node, widget_width, y, widget_height) {
Object.assign(
this.div.style,
get_position_style(ctx, widget_width, 88, node.size[1])
)
}
}
widget.div = $el('div', {})
widget.div.style.width = `120px`
document.body.appendChild(widget.div)
const inputDiv = (key, placeholder, preview) => {
let div = document.createElement('div')
const ip = document.createElement('input')
ip.type = 'file'
ip.className = `${'comfy-multiline-input'} ${placeholder}`
div.style = `display: flex;
align-items: center;
margin: 6px 8px;
margin-top: 0;`
ip.placeholder = placeholder
// ip.value = value
ip.style = `outline: none;
border: none;
padding: 4px;
width: 60%;cursor: pointer;
height: 32px;`
const label = document.createElement('label')
label.style = 'font-size: 10px;min-width:32px'
label.innerText = placeholder
div.appendChild(label)
div.appendChild(ip)
let that = this,
filename = new Date().getTime()
ip.addEventListener('change', async event => {
const file = event.target.files[0]
const reader = new FileReader()
filename = new Date().getTime()
// 读取文件内容
reader.onload = async e => {
const fileURL = URL.createObjectURL(file)
// console.log('文件URL: ', fileURL)
let html = `<model-viewer src="${fileURL}"
min-field-of-view="0deg" max-field-of-view="180deg"
shadow-intensity="1"
camera-controls
touch-action="pan-y">
<div class="controls">
<div>Variant: <select class="variant"></select></div>
<div>Material: <select class="material"></select></div>
<div>Material: <div class="material_img"> </div></div>
<div><button class="bg">BG</button></div>
</div></model-viewer>`
preview.innerHTML = html
if (that.size[1] < 400) {
that.setSize([that.size[0], that.size[1] + 300])
app.canvas.draw(true, true)
}
const modelViewerVariants = preview.querySelector('model-viewer')
const select = preview.querySelector('.variant')
const selectMaterial = preview.querySelector('.material')
const material_img = preview.querySelector('.material_img')
const bg = preview.querySelector('.bg')
if (modelViewerVariants) {
modelViewerVariants.style.width = `${that.size[0] - 24}px`
modelViewerVariants.style.height = `${that.size[1] - 48}px`
}
modelViewerVariants.addEventListener('load', async () => {
const names = modelViewerVariants.availableVariants
// 变量
for (const name of names) {
const option = document.createElement('option')
option.value = name
option.textContent = name
select.appendChild(option)
}
// Adds a default option.
if (names.length === 0) {
const option = document.createElement('option')
option.value = 'default'
option.textContent = 'Default'
select.appendChild(option)
}
// 材质
extractMaterial(
modelViewerVariants,
selectMaterial,
material_img
)
})
let timer = null
const delay = 500 // 延迟时间,单位为毫秒
async function checkCameraChange () {
let dd = getLocalData(key)
let base64Data = modelViewerVariants.toDataURL()
const contentType = getContentTypeFromBase64(base64Data)
const blob = await base64ToBlobFromURL(base64Data, contentType)
// const fileBlob = new Blob([e.target.result], { type: file.type });
let url = await uploadImage(blob, '.png')
// console.log(url)
// 材质贴图
let thumbUrl = material_img.getAttribute('src')
let tb = await base64ToBlobFromURL(thumbUrl)
let tUrl = await uploadImage(tb, '.png')
// console.log(tUrl)
let bg_blob = await base64ToBlobFromURL(
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mN88uXrPQAFwwK/6xJ6CQAAAABJRU5ErkJggg=='
)
let url_bg = await uploadImage(bg_blob, '.png')
if (!dd[that.id])
dd[that.id] = { url, bg: url_bg, material: tUrl }
dd[that.id] = { ...dd[that.id], url, material: tUrl }
setLocalDataOfWin(key, dd)
}
function startTimer () {
if (timer) clearTimeout(timer)
timer = setTimeout(checkCameraChange, delay)
}
modelViewerVariants.addEventListener('camera-change', startTimer)
select.addEventListener('input', event => {
modelViewerVariants.variantName =
event.target.value === 'default' ? null : event.target.value
// 材质
extractMaterial(
modelViewerVariants,
selectMaterial,
material_img
)
checkCameraChange()
})
selectMaterial.addEventListener('input', event => {
// console.log(selectMaterial.value)
material_img.setAttribute('src', selectMaterial.value)
if (selectMaterial.getAttribute('data-new-material')) {
let index =
~~selectMaterial.selectedOptions[0].getAttribute(
'data-index'
)
changeMaterial(
modelViewerVariants,
modelViewerVariants.model.materials[index],
selectMaterial.getAttribute('data-new-material')
)
}
checkCameraChange()
})
bg.addEventListener('click', () => {
// 创建一个input元素
var input = document.createElement('input')
input.type = 'file'
// 监听input的change事件
input.addEventListener('change', function () {
// 获取上传的文件
var file = input.files[0]
// 创建一个FileReader对象来读取文件
var reader = new FileReader()
// 监听FileReader的load事件
reader.addEventListener('load', async () => {
let base64 = reader.result
// 将读取的文件内容设置为div的背景
preview.style.backgroundImage = 'url(' + base64 + ')'
const contentType = getContentTypeFromBase64(base64)
const blob = await base64ToBlobFromURL(base64, contentType)
// const fileBlob = new Blob([e.target.result], { type: file.type });
let bg_url = await uploadImage(blob, '.png')
let bg_img = await createImage(base64)
let dd = getLocalData(key)
// console.log(dd[that.id],bg_url)
if (!dd[that.id]) dd[that.id] = { url: '', bg: bg_url }
dd[that.id] = {
...dd[that.id],
bg: bg_url,
bg_w: bg_img.naturalWidth,
bg_h: bg_img.naturalHeight
}
setLocalDataOfWin(key, dd)
// 更新尺寸
let w = that.size[0] - 24,
h = (w * bg_img.naturalHeight) / bg_img.naturalWidth
if (modelViewerVariants) {
modelViewerVariants.style.width = `${w}px`
modelViewerVariants.style.height = `${h}px`
}
preview.style.width = `${w}px`
})
// 读取文件
reader.readAsDataURL(file)
})
// 触发input的点击事件
input.click()
})
uploadWidget.value = await uploadWidget.serializeValue()
// 更新尺寸
let dd = getLocalData(key)
// console.log(dd[that.id],bg_url)
if (dd[that.id]) {
const { bg_w, bg_h } = dd[that.id]
if (bg_h && bg_w) {
let w = that.size[0] - 24,
h = (w * bg_h) / bg_w
if (modelViewerVariants) {
modelViewerVariants.style.width = `${w}px`
modelViewerVariants.style.height = `${h}px`
}
preview.style.width = `${w}px`
}
}
}
// 以文本形式读取文件
reader.readAsDataURL(file)
})
return div
}
let preview = document.createElement('div')
preview.className = 'preview'
preview.style = `margin-top: 12px;display: flex;
justify-content: center;
align-items: center;background-repeat: no-repeat;background-size: contain;`
let upload = inputDiv('_mixlab_3d_image', '3D Model', preview)
widget.div.appendChild(upload)
widget.div.appendChild(preview)
this.addCustomWidget(widget)
const onResize = this.onResize
let that = this
this.onResize = function () {
let modelViewerVariants = preview.querySelector('model-viewer')
// 更新尺寸
let dd = getLocalData('_mixlab_3d_image')
// console.log(dd[that.id],bg_url)
if (dd[that.id]) {
const { bg_w, bg_h } = dd[that.id]
if (bg_h && bg_w) {
let w = that.size[0] - 24,
h = (w * bg_h) / bg_w
if (modelViewerVariants) {
modelViewerVariants.style.width = `${w}px`
modelViewerVariants.style.height = `${h}px`
}
preview.style.width = `${w}px`
}
}
return onResize?.apply(this, arguments)
}
const onRemoved = this.onRemoved
this.onRemoved = () => {
upload.remove()
preview.remove()
widget.div.remove()
return onRemoved?.()
}
if (this.onResize) {
this.onResize(this.size)
}
// this.isVirtualNode = true
this.serialize_widgets = false //需要保存参数
}
const onExecuted = nodeType.prototype.onExecuted
nodeType.prototype.onExecuted = function (message) {
const r = onExecuted?.apply?.(this, arguments)
let div = this.widgets.filter(d => d.div)[0]?.div
console.log('Test', this.widgets)
let material = message.material[0]
if (material) {
const { filename, subfolder, type } = material
let src = api.apiURL(
`/view?filename=${encodeURIComponent(
filename
)}&type=${type}&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}`
)
const modelViewerVariants = div.querySelector('model-viewer')
const selectMaterial = div.querySelector('.material')
let index =
~~selectMaterial.selectedOptions[0].getAttribute('data-index')
selectMaterial.setAttribute('data-new-material', src)
changeMaterial(
modelViewerVariants,
modelViewerVariants.model.materials[index],
src
)
}
this.onResize?.(this.size)
return r
}
}
},
async loadedGraphNode (node, app) {
// Fires every time a node is constructed
// You can modify widgets/add handlers/etc here
const sleep = (t = 1000) => {
return new Promise((res, rej) => {
setTimeout(() => res(1), t)
})
}
if (node.type === '3DImage') {
// await sleep(0)
let widget = node.widgets.filter(w => w.name === 'upload-preview')[0]
let dd = getLocalData('_mixlab_3d_image')
let id = node.id
// console.log('3dImage load', node.widgets[0], node.widgets)
if (!dd[id]) return
let { url, bg } = dd[id]
if (!url) return
// let base64 = await parseImage(url)
let pre = widget.div.querySelector('.preview')
pre.style.width = `${node.size[0]}px`
pre.innerHTML = `
${url ? `<img src="${url}" style="width:100%"/>` : ''}
`
pre.style.backgroundImage = 'url(' + bg + ')'
const uploadWidget = node.widgets.filter(w => w.name == 'upload')[0]
uploadWidget.value = await uploadWidget.serializeValue()
}
}
})
+102 -15
View File
@@ -68,9 +68,8 @@ function speakText (text) {
// speakText('Hello, how are you?');
// #MixCopilot
const start = (element, id, startBtn) => {
startBtn.className='loading_mixlab'
const start = (element, id, startBtn, node) => {
startBtn.className = 'loading_mixlab'
window.recognition = new webkitSpeechRecognition()
@@ -95,15 +94,26 @@ const start = (element, id, startBtn) => {
localStorage.setItem('_mixlab_speech_recognition', JSON.stringify(data))
if (timeoutId) clearTimeout(timeoutId)
if (!window.recognition) return
timeoutId = setTimeout(function () {
console.log('结果传递::', result)
app.queuePrompt(0, 1)
// 把数据发送到chatgpt的输入prompt里
try {
const sendToId = node.widgets.filter(
w => w.name === 'Send to ChatGPT #'
)[0].value
app.graph
.getNodeById(sendToId)
.widgets.filter(w => w.name === 'prompt')[0].value = result
} catch (error) {}
setTimeout(() => app.queuePrompt(0, 1), 100)
window.recognition?.stop()
window.recognition = null;
startBtn.className=''
window.recognition = null
startBtn.className = ''
startBtn.innerText = 'START'
timeoutId = null
@@ -114,7 +124,7 @@ const start = (element, id, startBtn) => {
!window.recognition &&
window._mixlab_speech_synthesis_onend
) {
start(element, id, startBtn)
start(element, id, startBtn, node)
startBtn.innerText = 'STOP'
if (intervalId) {
clearInterval(intervalId)
@@ -171,13 +181,21 @@ app.registerExtension({
nodeType.prototype.onNodeCreated = function () {
orig_nodeCreated?.apply(this, arguments)
const sendTo = ComfyWidgets.INT(
this,
'Send to ChatGPT #',
['INT', { default: 0 }],
app
)
// console.log('sendTo',sendTo)
const widget = {
type: 'div',
name: 'chatgptdiv',
draw (ctx, node, widget_width, y, widget_height) {
Object.assign(
this.div.style,
get_position_style(ctx, widget_width, 44, node.size[1])
get_position_style(ctx, widget_width, 78, node.size[1])
)
}
}
@@ -189,7 +207,14 @@ app.registerExtension({
const inputDiv = (key, placeholder) => {
let div = document.createElement('div')
const startBtn = document.createElement('button')
const textArea = document.createElement('textarea')
textArea.placeholder = 'speak text'
// sendTo.type='range';
// sendTo.min=0;
// sendTo.max=2000;
// sendTo.step=1;
// sendTo.className='comfy-multiline-input'
textArea.className = `${'comfy-multiline-input'} ${placeholder}`
@@ -201,13 +226,18 @@ app.registerExtension({
margin: 0px 8px 6px;`
startBtn.style = `
outline: none;
border: none;
padding: 4px; `
margin-top:48px;
background-color: var(--comfy-input-bg);
border-radius: 8px;
border-color: var(--border-color);
border-style: solid;
color: var(--descrip-text);
`
startBtn.innerText = 'START'
div.appendChild(startBtn)
// div.appendChild(sendTo);
div.appendChild(textArea)
startBtn.addEventListener('click', () => {
@@ -215,13 +245,17 @@ app.registerExtension({
window.recognition.stop()
window.recognition = null
startBtn.innerText = 'START'
startBtn.className=''
startBtn.className = ''
} else {
start(textArea, this.id, startBtn)
start(textArea, this.id, startBtn, this)
startBtn.innerText = 'STOP'
}
})
// sendTo.addEventListener('change',()=>{
// console.log(sendTo.value)
// })
return div
}
@@ -239,16 +273,69 @@ app.registerExtension({
this.serialize_widgets = true //需要保存参数
}
// const onGraphConfigured=nodeType.prototype.onGraphConfigured;
// nodeType.prototype.onGraphConfigured = function (message) {
// onGraphConfigured?.apply(this, arguments)
// console.log('###SpeechRecognition onGraphConfigured',this,message)
// }
const onExecuted = nodeType.prototype.onExecuted
nodeType.prototype.onExecuted = function (message) {
onExecuted?.apply(this, arguments)
// console.log('this.widgets', this.widgets)
try {
// 是否根据start by 开启
let open = message.start_by[0] > 0
if (open) {
const div = this.widgets.filter(w => w.name == 'chatgptdiv')[0].div
const startBtn = div.querySelector('button')
let textArea = div.querySelector('textarea')
if (open && !window.recognition) {
start(textArea, this.id, startBtn, this)
startBtn.innerText = 'STOP'
} else if (!open && window.recognition) {
window.recognition.stop()
window.recognition = null
startBtn.innerText = 'START'
startBtn.className = ''
}
}
} catch (error) {
console.log('###SpeechRecognition', error)
}
}
}
},
async loadedGraphNode (node, app) {
if (node.type === 'SpeechRecognition') {
let data = getLocalData('_mixlab_speech_recognition')
// console.log('_mixlab_speech_recognition', node.widgets)
// console.log('_mixlab_speech_recognition', node )
let div = node.widgets.filter(f => f.type === 'div')[0]
if (div && data[node.id]) {
div.div.querySelector('textarea').value = data[node.id]
}
try {
let open = node.widgets_values[1] > 0
if (open) {
const div = node.widgets.filter(w => w.name == 'chatgptdiv')[0].div
const startBtn = div.querySelector('button')
let textArea = div.querySelector('textarea')
if (open && !window.recognition) {
start(textArea, node.id, startBtn, node)
startBtn.innerText = 'STOP'
} else if (!open && window.recognition) {
window.recognition.stop()
window.recognition = null
startBtn.innerText = 'START'
startBtn.className = ''
}
}
} catch (error) {
console.log('###SpeechRecognition', error)
}
}
}
})
+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.4.1'
const version = 'v0.4.2'
fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
.then(response => response.json())
+1 -1
View File
@@ -61,7 +61,7 @@ app.registerExtension({
async getCustomWidgets (app) {
return {
KEY (node, inputName, inputData, app) {
// console.log('##node', node)
console.log('##inputData', inputData)
const widget = {
type: inputData[0], // the type, CHEESE
name: inputName, // the name, slice
+2 -389
View File
@@ -3,9 +3,7 @@ import { api } from '../../../scripts/api.js'
import { ComfyWidgets } from '../../../scripts/widgets.js'
import { $el } from '../../../scripts/ui.js'
async function uploadImage (blob, fileType = '.svg', filename) {
// const blob = await (await fetch(src)).blob();
const body = new FormData()
body.append(
@@ -434,392 +432,7 @@ app.registerExtension({
const uploadWidget = node.widgets.filter(w => w.name == 'upload')[0]
uploadWidget.value = await uploadWidget.serializeValue()
// let h=~~getComputedStyle(widget.div).height.replace('px','');
// let w=~~getComputedStyle(widget.div).width.replace('px','');
// // console.log('svg', w,h,node.size)
// node.setSize([
// w,h
// ])
// app.graph.setDirtyCanvas(true)
// console.log(node.widgets_values)
}
}
})
app.registerExtension({
name: 'Mixlab.image.3DImage',
async getCustomWidgets (app) {
return {
THREED (node, inputName, inputData, app) {
// console.log('##node', node, inputName, inputData)
const widget = {
type: inputData[0], // the type, CHEESE
name: inputName, // the name, slice
size: [128, 88], // a default size
draw (ctx, node, width, y) {},
computeSize (...args) {
return [128, 88] // a method to compute the current size of the widget
},
async serializeValue (nodeId, widgetIndex) {
let d = getLocalData('_mixlab_3d_image')
// console.log('serializeValue',d)
if (d && d[node.id]) {
let { url, bg } = d[node.id]
let base64 = await parseImage(url)
let bg_base64 = await parseImage(bg)
return JSON.parse(
JSON.stringify({ image: base64, bg_image: bg_base64 })
)
} else {
return {}
}
}
}
node.addCustomWidget(widget)
return widget
}
}
},
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == '3DImage') {
const orig_nodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = async function () {
orig_nodeCreated?.apply(this, arguments)
const uploadWidget = this.widgets.filter(w => w.name == 'upload')[0]
// console.log('3d nodeData', this.inputs)
const widget = {
type: 'div',
name: 'upload-preview',
draw (ctx, node, widget_width, y, widget_height) {
Object.assign(
this.div.style,
get_position_style(ctx, widget_width, 88, node.size[1])
)
}
}
widget.div = $el('div', {})
widget.div.style.width = `120px`
document.body.appendChild(widget.div)
const inputDiv = (key, placeholder, preview) => {
let div = document.createElement('div')
const ip = document.createElement('input')
ip.type = 'file'
ip.className = `${'comfy-multiline-input'} ${placeholder}`
div.style = `display: flex;
align-items: center;
margin: 6px 8px;
margin-top: 0;`
ip.placeholder = placeholder
// ip.value = value
ip.style = `outline: none;
border: none;
padding: 4px;
width: 60%;cursor: pointer;
height: 32px;`
const label = document.createElement('label')
label.style = 'font-size: 10px;min-width:32px'
label.innerText = placeholder
div.appendChild(label)
div.appendChild(ip)
let that = this,
filename = new Date().getTime()
ip.addEventListener('change', event => {
const file = event.target.files[0]
const reader = new FileReader()
filename = new Date().getTime()
// 读取文件内容
reader.onload = async e => {
const fileURL = URL.createObjectURL(file)
// console.log('文件URL: ', fileURL)
let html = `<model-viewer src="${fileURL}"
min-field-of-view="0deg" max-field-of-view="180deg"
shadow-intensity="1"
camera-controls
touch-action="pan-y">
<div class="controls">
<div>Variant: <select class="variant"></select></div>
<div><button class="bg">BG</button></div>
</div></model-viewer>`
preview.innerHTML = html
if (that.size[1] < 400) {
that.setSize([that.size[0], that.size[1] + 300])
app.canvas.draw(true, true)
};
const modelViewerVariants = preview.querySelector('model-viewer')
const select = preview.querySelector('.variant')
const bg = preview.querySelector('.bg')
if (modelViewerVariants) {
modelViewerVariants.style.width = `${that.size[0] - 24}px`
modelViewerVariants.style.height = `${that.size[1] - 48}px`
}
modelViewerVariants.addEventListener('load', () => {
const names = modelViewerVariants.availableVariants
for (const name of names) {
const option = document.createElement('option')
option.value = name
option.textContent = name
select.appendChild(option)
}
// Adds a default option.
const option = document.createElement('option')
option.value = 'default'
option.textContent = 'Default'
select.appendChild(option)
})
let timer = null
const delay = 500 // 延迟时间,单位为毫秒
async function checkCameraChange () {
let dd = getLocalData(key)
let w, h
let base64Data = modelViewerVariants.toDataURL()
// if (dd[that.id]) {
// w = dd[that.id].bg_w
// h = dd[that.id].bg_h
// }
// // 在这里触发相机停止变化的事件
// // console.log('在这里触发相机停止变化的事件')
// // let base64Data = modelViewerVariants.toDataURL()
// if (w && h) {
// base64Data = await exportModelViewerImage(
// modelViewerVariants.displaycanvas,
// w,
// h
// )
// } else {
// }
const contentType = getContentTypeFromBase64(base64Data)
const blob = await base64ToBlobFromURL(base64Data, contentType)
// const fileBlob = new Blob([e.target.result], { type: file.type });
let url = await uploadImage(blob, '.png')
// console.log(url)
if (!dd[that.id]) dd[that.id] = { url, bg: '' }
dd[that.id] = { ...dd[that.id], url }
setLocalDataOfWin(key, dd)
}
function startTimer () {
if (timer) clearTimeout(timer)
timer = setTimeout(checkCameraChange, delay)
}
modelViewerVariants.addEventListener('camera-change', startTimer)
select.addEventListener('input', event => {
modelViewerVariants.variantName =
event.target.value === 'default' ? null : event.target.value
checkCameraChange()
})
bg.addEventListener('click', () => {
// 创建一个input元素
var input = document.createElement('input')
input.type = 'file'
// 监听input的change事件
input.addEventListener('change', function () {
// 获取上传的文件
var file = input.files[0]
// 创建一个FileReader对象来读取文件
var reader = new FileReader()
// 监听FileReader的load事件
reader.addEventListener('load', async () => {
let base64 = reader.result
// 将读取的文件内容设置为div的背景
preview.style.backgroundImage = 'url(' + base64 + ')'
const contentType = getContentTypeFromBase64(base64)
const blob = await base64ToBlobFromURL(base64, contentType)
// const fileBlob = new Blob([e.target.result], { type: file.type });
let bg_url = await uploadImage(blob, '.png')
let bg_img = await createImage(base64)
let dd = getLocalData(key)
// console.log(dd[that.id],bg_url)
if (!dd[that.id]) dd[that.id] = { url: '', bg: bg_url }
dd[that.id] = {
...dd[that.id],
bg: bg_url,
bg_w: bg_img.naturalWidth,
bg_h: bg_img.naturalHeight
}
setLocalDataOfWin(key, dd)
// 更新尺寸
let w = that.size[0] - 24,
h = (w * bg_img.naturalHeight) / bg_img.naturalWidth
if (modelViewerVariants) {
modelViewerVariants.style.width = `${w}px`
modelViewerVariants.style.height = `${h}px`
}
preview.style.width = `${w}px`
})
// 读取文件
reader.readAsDataURL(file)
})
// 触发input的点击事件
input.click()
})
uploadWidget.value = await uploadWidget.serializeValue()
// 更新尺寸
let dd = getLocalData(key)
// console.log(dd[that.id],bg_url)
if (dd[that.id]) {
const { bg_w, bg_h } = dd[that.id]
if (bg_h && bg_w) {
let w = that.size[0] - 24,
h = (w * bg_h) / bg_w
if (modelViewerVariants) {
modelViewerVariants.style.width = `${w}px`
modelViewerVariants.style.height = `${h}px`
}
preview.style.width = `${w}px`
}
}
}
// 以文本形式读取文件
reader.readAsDataURL(file)
})
return div
}
let preview = document.createElement('div')
preview.className = 'preview'
preview.style = `margin-top: 12px;display: flex;
justify-content: center;
align-items: center;background-repeat: no-repeat;background-size: contain;`
let upload = inputDiv('_mixlab_3d_image', '3D Model', preview)
widget.div.appendChild(upload)
widget.div.appendChild(preview)
this.addCustomWidget(widget)
const onResize = this.onResize
let that=this;
this.onResize = function () {
let modelViewerVariants = preview.querySelector('model-viewer')
// 更新尺寸
let dd = getLocalData('_mixlab_3d_image')
// console.log(dd[that.id],bg_url)
if (dd[that.id]) {
const { bg_w, bg_h } = dd[that.id]
if (bg_h && bg_w) {
let w = that.size[0] - 24,
h = (w * bg_h) / bg_w
if (modelViewerVariants) {
modelViewerVariants.style.width = `${w}px`
modelViewerVariants.style.height = `${h}px`
}
preview.style.width = `${w}px`
}
}
return onResize?.apply(this, arguments)
}
const onRemoved = this.onRemoved
this.onRemoved = () => {
upload.remove()
preview.remove()
widget.div.remove()
return onRemoved?.()
}
if (this.onResize) {
this.onResize(this.size)
}
this.serialize_widgets = true //需要保存参数
}
}
},
async loadedGraphNode (node, app) {
// Fires every time a node is constructed
// You can modify widgets/add handlers/etc here
const sleep = (t = 1000) => {
return new Promise((res, rej) => {
setTimeout(() => res(1), t)
})
}
if (node.type === '3DImage') {
// await sleep(0)
let widget = node.widgets.filter(w => w.name === 'upload-preview')[0]
let dd = getLocalData('_mixlab_3d_image')
let id = node.id
// console.log('3dImage load', node.widgets[0], node.widgets)
if (!dd[id]) return
let { url, bg } = dd[id]
if (!url) return
// let base64 = await parseImage(url)
let pre = widget.div.querySelector('.preview')
pre.style.width = `${node.size[0]}px`
pre.innerHTML = `
${url ? `<img src="${url}" style="width:100%"/>` : ''}
`
pre.style.backgroundImage = 'url(' + bg + ')'
const uploadWidget = node.widgets.filter(w => w.name == 'upload')[0]
uploadWidget.value = await uploadWidget.serializeValue()
// let h=~~getComputedStyle(widget.div).height.replace('px','');
// let w=~~getComputedStyle(widget.div).width.replace('px','');
// // console.log('svg', w,h,node.size)
// node.setSize([
// w,h
// ])
// app.graph.setDirtyCanvas(true)
// console.log(node.widgets_values)
}
}
})
})
+227 -20
View File
@@ -69,12 +69,12 @@ const parseSvg = async svgContent => {
Array.from(rectElements, (rectElement, i) => {
// 获取rect元素的属性值
var x = ~~(rectElement.getAttribute('x')||0);
var y = ~~(rectElement.getAttribute('y')||0);
var x = ~~(rectElement.getAttribute('x') || 0)
var y = ~~(rectElement.getAttribute('y') || 0)
var width = ~~rectElement.getAttribute('width')
var height = ~~rectElement.getAttribute('height')
// console.log('rectElements',rectElement,x,y,width,height)
if (x != undefined && y != undefined&&width&&height) {
if (x != undefined && y != undefined && width && height) {
// 创建一个新的canvas元素
var canvas = document.createElement('canvas')
canvas.width = width
@@ -101,7 +101,7 @@ const parseSvg = async svgContent => {
image: base64,
mask: base64,
type: 'base64',
_t:'rect'
_t: 'rect'
}
// 将处理后的数据添加到数组中
@@ -115,9 +115,9 @@ const parseSvg = async svgContent => {
if (!(svgWidth && svgHeight)) {
// viewBox
let viewBox = svgElement.viewBox.baseVal
svgWidth =viewBox.width
svgHeight =viewBox.height
svgWidth = viewBox.width
svgHeight = viewBox.height
}
// 创建一个新的canvas元素
@@ -147,15 +147,146 @@ const parseSvg = async svgContent => {
image: base64,
mask: base64,
type: 'base64',
_t:'canvas'
_t: 'canvas'
}
data.push(rectData)
// 打印处理后的数据
console.log('layers',{ data, image: base64, svgElement })
console.log('layers', { data, image: base64, svgElement })
return { data, image: base64, svgElement }
}
async function setArea (cw, ch, base64, data, fn) {
let displayHeight = Math.round(window.screen.availHeight * 0.6)
let div = document.createElement('div')
div.innerHTML = `
<div id='ml_overlay' style='position: absolute;top:0;background: #251f1fc4;
height: 100vh;
z-index:999999;
width: 100%;'>
<img id='ml_video' style='position: absolute;
height: ${displayHeight}px;user-select: none;
-webkit-user-drag: none;
outline: 2px solid #eaeaea;
box-shadow: 8px 9px 17px #575757;' />
<div id='ml_selection' style='position: absolute;
border: 2px dashed red;
pointer-events: none;'></div>
</div>`
// document.body.querySelector('#ml_overlay')
document.body.appendChild(div)
// let canvas = document.createElement('canvas')
// canvas.width = cw
// canvas.height = ch
let img = div.querySelector('#ml_video')
let overlay = div.querySelector('#ml_overlay')
let selection = div.querySelector('#ml_selection')
let startX, startY, endX, endY
let start = false
// Set video source
img.src = base64
// canvas.toDataURL();
// init area
// const data = getSetAreaData()
let x = 0,
y = 0,
width = (cw * displayHeight) / ch,
height = displayHeight
let imgWidth = cw
let imgHeight = ch
if (data && data.width > 0 && data.height > 0) {
// 相同尺寸窗口,恢复选区
x = (width * data.x) / imgWidth
y = (height * data.y) / imgHeight
width = (width * data.width) / imgWidth
height = (height * data.height) / imgHeight
}
selection.style.left = x + 'px'
selection.style.top = y + 'px'
selection.style.width = width + 'px'
selection.style.height = height + 'px'
// Add mouse events
img.addEventListener('mousedown', startSelection)
img.addEventListener('mousemove', updateSelection)
img.addEventListener('mouseup', endSelection)
overlay.addEventListener('click', remove)
function remove () {
overlay.removeEventListener('click', remove)
img.removeEventListener('mousedown', startSelection)
img.removeEventListener('mousemove', updateSelection)
img.removeEventListener('mouseup', endSelection)
div.remove()
}
function startSelection (event) {
if (start == false) {
startX = event.clientX
startY = event.clientY
updateSelection(event)
start = true
} else {
}
}
function updateSelection (event) {
endX = event.clientX
endY = event.clientY
// Calculate width, height, and coordinates
let width = Math.abs(endX - startX)
let height = Math.abs(endY - startY)
let left = Math.min(startX, endX)
let top = Math.min(startY, endY)
// Set selection style
selection.style.left = left + 'px'
selection.style.top = top + 'px'
selection.style.width = width + 'px'
selection.style.height = height + 'px'
}
function endSelection (event) {
endX = event.clientX
endY = event.clientY
// 获取img元素的真实宽度和高度
let imgWidth = img.naturalWidth
let imgHeight = img.naturalHeight
// 换算起始坐标
let realStartX = (startX / img.offsetWidth) * imgWidth
let realStartY = (startY / img.offsetHeight) * imgHeight
// 换算起始坐标
let realEndX = (endX / img.offsetWidth) * imgWidth
let realEndY = (endY / img.offsetHeight) * imgHeight
startX = realStartX
startY = realStartY
endX = realEndX
endY = realEndY
// Calculate width, height, and coordinates
let width = Math.round(Math.abs(endX - startX))
let height = Math.round(Math.abs(endY - startY))
let left = Math.round(Math.min(startX, endX))
let top = Math.round(Math.min(startY, endY))
if (width <= 0 && height <= 0) return remove()
if (fn) fn(left, top, width, height)
remove()
}
}
app.registerExtension({
name: 'Mixlab.layer.ShowLayer',
async getCustomWidgets (app) {
@@ -199,8 +330,7 @@ app.registerExtension({
const findNode = nodeId => {
let node = app.graph._nodes_by_id[nodeId]
if (node?.type == 'Reroute') {
let linkId =node.inputs.filter(i=>i.type=='*')[0].link
let linkId = node.inputs.filter(i => i.type == '*')[0].link
nodeId = app.graph.links.filter(link => link.id == linkId)[0]
?.origin_id
return findNode(nodeId)
@@ -211,15 +341,17 @@ app.registerExtension({
// 获取layers数据
const getLayers = async () => {
console.log('getLayers1',this.inputs.filter(ip => ip.name === 'layers'))
console.log(
'getLayers1',
this.inputs.filter(ip => ip.name === 'layers')
)
let linkId = this.inputs.filter(ip => ip.name === 'layers')[0].link
let nodeId = app.graph.links?.filter(link => link.id == linkId)[0]
?.origin_id;
?.origin_id
if(nodeId){
if (nodeId) {
nodeId = findNode(nodeId)
}
// let node = app.graph._nodes_by_id[nodeId]
// if (node?.type == 'Reroute') {
@@ -227,18 +359,18 @@ app.registerExtension({
// nodeId = app.graph.links.filter(link => link.id == linkId)[0]
// ?.origin_id
// }
let d = getLocalData('_mixlab_svg_image')
console.log('test',d[nodeId])
console.log('test', d[nodeId])
if (d[nodeId]) {
let url = d[nodeId]
let dt = await fetch(url)
let svgStr = await dt.text()
const { data } = (await parseSvg(svgStr)) || {}
console.log('fetch',data)
console.log('fetch', data)
return data
} else {
return []
@@ -350,3 +482,78 @@ app.registerExtension({
}
}
})
app.registerExtension({
name: 'Mixlab.layer.NewLayer',
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeData.name === 'NewLayer') {
const orig_nodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = async function () {
orig_nodeCreated?.apply(this, arguments)
let b = this.widgets.filter(w => w.type === 'button')[0]
// const [w, h, base64] = canvas
if (!b) {
const updateValue = (x1, y1, w1, h1) => {
if (this.widgets) {
for (const widget of this.widgets) {
if (widget.name === 'x') {
widget.value = x1
}
if (widget.name === 'y') {
widget.value = y1
}
if (widget.name === 'width') {
widget.value = w1
}
if (widget.name === 'height') {
widget.value = h1
}
}
}
}
this.addWidget('button', 'Set Area', '', () => {
let data = {}
for (const widget of this.widgets) {
if (widget.name === 'x') {
data.x = widget.value
}
if (widget.name === 'y') {
data.y = widget.value
}
if (widget.name === 'width') {
data.width = widget.value
}
if (widget.name === 'height') {
data.height = widget.value
}
}
try {
let linkId = this.inputs[3].link
let nodeId = app.graph.links[linkId].origin_id
// console.log(linkId,this.inputs)
let im = app.graph.getNodeById(nodeId).imgs[0]
let src = im.src
setArea(im.naturalWidth, im.naturalHeight, src, data, updateValue)
} catch (error) {}
})
}
}
const onRemoved = this.onRemoved
this.onRemoved = () => {
// let b = this.widgets.filter(w => w.type === 'button')[0];
return onRemoved?.()
}
if (this.onResize) {
this.onResize(this.size)
}
this.serialize_widgets = true //需要保存参数
}
}
})
+39 -56
View File
@@ -461,7 +461,7 @@ async function requestCamera () {
/*
A method that returns the required style for the html
*/
function get_position_style (ctx, widget_width, y, node_height) {
function get_position_style (ctx, widget_width, y, node_height, top) {
const MARGIN = 4 // the margin around the html element
/* Create a transform that deals with all the scrolling and zooming */
@@ -478,7 +478,7 @@ function get_position_style (ctx, widget_width, y, node_height) {
transformOrigin: '0 0',
transform: transform,
left: `0`,
top: `0`,
top: `${top}px`,
cursor: 'pointer',
position: 'absolute',
maxWidth: `${widget_width - MARGIN * 2}px`,
@@ -585,9 +585,6 @@ app.registerExtension({
},
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == 'ScreenShare') {
/*
Hijack the onNodeCreated call to add our widget
*/
const orig_nodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = function () {
orig_nodeCreated?.apply(this, arguments)
@@ -603,7 +600,8 @@ app.registerExtension({
ctx,
widget_width,
widget_height * 5,
node.size[1]
node.size[1],
40
)
)
}
@@ -693,22 +691,22 @@ app.registerExtension({
}
})
widget.refreshInput = $el('input', {
placeholder: ' Refresh rate:200 ms',
type: 'number',
min: 100,
step: 100,
style: {
cursor: 'pointer',
padding: '8px 24px',
fontWeight: '300',
margin: '2px',
color: 'var(--descrip-text)',
backgroundColor: 'var(--comfy-input-bg)'
}
});
widget.refreshInput.className='comfy-multiline-input'
// widget.refreshInput = $el('input', {
// placeholder: ' Refresh rate:200 ms',
// type: 'number',
// min: 100,
// step: 100,
// style: {
// cursor: 'pointer',
// padding: '8px 24px',
// fontWeight: '300',
// margin: '2px',
// color: 'var(--descrip-text)',
// backgroundColor: 'var(--comfy-input-bg)'
// }
// });
// widget.refreshInput.className='comfy-multiline-input'
widget.liveBtn = $el('button', {
innerText: 'Live Run',
style: {
@@ -734,7 +732,7 @@ app.registerExtension({
widget.shareDiv.appendChild(widget.shareBtn)
widget.shareDiv.appendChild(widget.shareOfWebCamBtn)
widget.card.appendChild(widget.openFloatingWinBtn)
widget.card.appendChild(widget.refreshInput)
// widget.card.appendChild(widget.refreshInput)
widget.card.appendChild(widget.liveBtn)
const toggleShare = async (isCamera = false) => {
@@ -894,11 +892,11 @@ app.registerExtension({
toggleShare()
})
widget.refreshInput.addEventListener('change', async () => {
window._mixlab_screen_refresh_rate = Math.round(
widget.refreshInput.value
)
})
// widget.refreshInput.addEventListener('change', async () => {
// window._mixlab_screen_refresh_rate = Math.round(
// widget.refreshInput.value
// )
// })
widget.liveBtn.addEventListener('click', async () => {
if (window._mixlab_stopLive) {
@@ -939,12 +937,21 @@ app.registerExtension({
widget.shareBtn.remove()
widget.liveBtn.remove()
widget.card.remove()
widget.refreshInput.remove()
// widget.refreshInput.remove()
widget.previewArea.remove()
widget.previewCard.remove()
}
this.serialize_widgets = true
}
const onExecuted = nodeType.prototype.onExecuted
nodeType.prototype.onExecuted = function (message) {
onExecuted?.apply(this, arguments)
// console.log('###ScreenShare', this, message.refresh_rate)
window._mixlab_screen_refresh_rate = Math.round(
message.refresh_rate[0] || 500
)
}
}
}
})
@@ -1036,6 +1043,7 @@ async function setArea (src) {
div.innerHTML = `
<div id='ml_overlay' style='position: absolute;top:0;background: #251f1fc4;
height: 100vh;
z-index:999999;
width: 100%;'>
<img id='ml_video' style='position: absolute;
height: ${displayHeight}px;user-select: none;
@@ -1067,12 +1075,7 @@ async function setArea (src) {
height = displayHeight
let imgWidth = im.naturalWidth
let imgHeight = im.naturalHeight
// console.log(
// '#screen_share::使用上一次选区 selection',
// data,
// imgWidth,
// img.width
// )
if (
data &&
data.width > 0 &&
@@ -1086,9 +1089,6 @@ async function setArea (src) {
y = (img.height * data.y) / data.imgHeight
width = (img.width * data.width) / data.imgWidth
height = (img.height * data.height) / data.imgHeight
// imgWidth = data.imgWidth
// imgHeight = data.imgHeight;
// console.log('#screen_share::使用上一次选区 selection', x, y, width, height)
}
selection.style.left = x + 'px'
@@ -1153,9 +1153,6 @@ async function setArea (src) {
let realEndX = (endX / img.offsetWidth) * imgWidth
let realEndY = (endY / img.offsetHeight) * imgHeight
// 输出结果到控制台
// console.log('真实宽度: ' + realWidth)
// console.log('真实高度: ' + realHeight)
startX = realStartX
startY = realStartY
endX = realEndX
@@ -1165,19 +1162,6 @@ async function setArea (src) {
let height = Math.abs(endY - startY)
let left = Math.min(startX, endX)
let top = Math.min(startY, endY)
// Output results to console
// console.log('坐标位置: (' + left + ', ' + top + ')')
// console.log('宽度: ' + width)
// console.log('高度: ' + height)
// img.removeEventListener('mousedown', startSelection)
// img.removeEventListener('mousemove', updateSelection)
// img.removeEventListener('mouseup', endSelection)
// window._mixlab_screen_x = left
// window._mixlab_screen_y = top
// window._mixlab_screen_width = width
// window._mixlab_screen_height = height
if (width <= 0 && height <= 0) return remove()
@@ -1189,7 +1173,6 @@ async function setArea (src) {
window._mixlab_screen_webcamVideo,
!window._mixlab_screen_live
)
remove()
}
}
@@ -1238,7 +1221,7 @@ app.registerExtension({
draw (ctx, node, widget_width, y, widget_height) {
Object.assign(
this.card.style,
get_position_style(ctx, widget_width, y, node.size[1])
get_position_style(ctx, widget_width, y, node.size[1], 0)
)
}
}
+120 -107
View File
@@ -1,10 +1,8 @@
import { app } from '../../../scripts/app.js'
import { api } from '../../../scripts/api.js'
import { ComfyWidgets } from '../../../scripts/widgets.js'
import { $el } from '../../../scripts/ui.js'
import { addValueControlWidget } from "../../../scripts/widgets.js";
const getLocalData = key => {
let data = {}
@@ -47,122 +45,137 @@ function get_position_style (ctx, widget_width, y, node_height) {
}
}
app.registerExtension({
name: 'Mixlab.utils.Color',
async getCustomWidgets (app) {
return {
TCOLOR (node, inputName, inputData, app) {
// console.log('##node', node)
const widget = {
type: inputData[0], // the type, CHEESE
name: inputName, // the name, slice
size: [128, 32], // a default size
draw (ctx, node, width, y) {},
computeSize (...args) {
return [128, 32] // a method to compute the current size of the widget
},
async serializeValue (nodeId, widgetIndex) {
let data = getLocalData('_mixlab_utils_color')
return data[node.id] || '#000000'
}
name: 'Mixlab.utils.Color',
async getCustomWidgets (app) {
return {
TCOLOR (node, inputName, inputData, app) {
// console.log('##node', node)
const widget = {
type: inputData[0], // the type, CHEESE
name: inputName, // the name, slice
size: [128, 32], // a default size
draw (ctx, node, width, y) {},
computeSize (...args) {
return [128, 32] // a method to compute the current size of the widget
},
async serializeValue (nodeId, widgetIndex) {
let data = getLocalData('_mixlab_utils_color')
return data[node.id] || '#000000'
}
// widget.something = something; // maybe adds stuff to it
node.addCustomWidget(widget) // adds it to the node
return widget // and returns it.
}
// widget.something = something; // maybe adds stuff to it
node.addCustomWidget(widget) // adds it to the node
return widget // and returns it.
}
},
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == 'Color') {
const orig_nodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = function () {
orig_nodeCreated?.apply(this, arguments)
console.log('Color nodeData', this.widgets)
const widget = {
type: 'div',
name: 'input_color',
draw (ctx, node, widget_width, y, widget_height) {
Object.assign(
this.div.style,
get_position_style(
ctx,
widget_width,
44,
node.size[1]
)
)
}
}
},
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == 'Color') {
const orig_nodeCreated = nodeType.prototype.onNodeCreated
nodeType.prototype.onNodeCreated = function () {
orig_nodeCreated?.apply(this, arguments)
console.log('Color nodeData', this.widgets)
const widget = {
type: 'div',
name: 'input_color',
draw (ctx, node, widget_width, y, widget_height) {
Object.assign(
this.div.style,
get_position_style(ctx, widget_width, 44, node.size[1])
)
}
widget.div = $el('div', {})
document.body.appendChild(widget.div)
const inputDiv = (key, placeholder, value) => {
let div = document.createElement('div')
const ip = document.createElement('input')
ip.type = 'color'
ip.className = `${'comfy-multiline-input'} ${placeholder}`
div.style = `display: flex;
}
widget.div = $el('div', {})
document.body.appendChild(widget.div)
const inputDiv = (key, placeholder, value) => {
let div = document.createElement('div')
const ip = document.createElement('input')
ip.type = 'color'
ip.className = `${'comfy-multiline-input'} ${placeholder}`
div.style = `display: flex;
align-items: center;
margin: 6px 8px;
margin-top: 0;`
ip.placeholder = placeholder
ip.value = value
ip.style = `outline: none;
ip.placeholder = placeholder
ip.value = value
ip.style = `outline: none;
border: none;
padding: 4px;
width: 100%;cursor: pointer;
height: 32px;`
const label = document.createElement('label')
label.style = 'font-size: 10px;min-width:32px'
label.innerText = placeholder
div.appendChild(label)
div.appendChild(ip)
ip.addEventListener('change', () => {
let data = getLocalData(key)
data[this.id] = ip.value.trim()
localStorage.setItem(key, JSON.stringify(data))
// console.log(this.id, ip.value.trim())
})
return div
}
let inputColor = inputDiv('_mixlab_utils_color', 'Color', '#000000')
widget.div.appendChild(inputColor)
this.addCustomWidget(widget)
const onRemoved = this.onRemoved
this.onRemoved = () => {
inputColor.remove()
widget.div.remove()
return onRemoved?.()
}
this.serialize_widgets = true //需要保存参数
const label = document.createElement('label')
label.style = 'font-size: 10px;min-width:32px'
label.innerText = placeholder
div.appendChild(label)
div.appendChild(ip)
ip.addEventListener('change', () => {
let data = getLocalData(key)
data[this.id] = ip.value.trim()
localStorage.setItem(key, JSON.stringify(data))
// console.log(this.id, ip.value.trim())
})
return div
}
}
},
async loadedGraphNode (node, app) {
// Fires every time a node is constructed
// You can modify widgets/add handlers/etc here
if (node.type === 'Color') {
let widget = node.widgets.filter(w => w.div)[0]
let data = getLocalData('_mixlab_utils_color')
let id = node.id
widget.div.querySelector('.Color').value = data[id] || '#000000'
let inputColor = inputDiv('_mixlab_utils_color', 'Color', '#000000')
widget.div.appendChild(inputColor)
this.addCustomWidget(widget)
const onRemoved = this.onRemoved
this.onRemoved = () => {
inputColor.remove()
widget.div.remove()
return onRemoved?.()
}
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 === 'Color') {
let widget = node.widgets.filter(w => w.div)[0]
let data = getLocalData('_mixlab_utils_color')
let id = node.id
widget.div.querySelector('.Color').value = data[id] || '#000000'
}
}
})
app.registerExtension({
name: 'Mixlab.utils.TextToNumber',
async beforeRegisterNodeDef (nodeType, nodeData, app) {
if (nodeType.comfyClass == 'TextToNumber') {
const onExecuted = nodeType.prototype.onExecuted
nodeType.prototype.onExecuted = function (message) {
onExecuted?.apply(this, arguments)
const random_number=this.widgets.filter(w=>w.name==='random_number')[0]
if(random_number.value==='enable'){
const n=this.widgets.filter(w=>w.name==='number')[0]
n.value=message.num[0]
}
console.log('TextToNumber', random_number.value)
}
}
}
})
+10 -9
View File
@@ -295,7 +295,7 @@
"Node name for S&R": "KSampler"
},
"widgets_values": [
1115769645491668,
482859286431021,
"randomize",
4,
1.6,
@@ -479,10 +479,10 @@
1928,
295
],
"size": {
"0": 315,
"1": 58
},
"size": [
315,
58
],
"flags": {},
"order": 12,
"mode": 0,
@@ -507,10 +507,10 @@
-65,
446
],
"size": {
"0": 315,
"1": 170
},
"size": [
312.78457519531213,
606.2132135620109
],
"flags": {},
"order": 3,
"mode": 0,
@@ -552,6 +552,7 @@
},
"widgets_values": [
null,
2018,
null,
null,
null,
+67 -100
View File
File diff suppressed because one or more lines are too long
+314
View File
@@ -0,0 +1,314 @@
{
"last_node_id": 10,
"last_link_id": 16,
"nodes": [
{
"id": 9,
"type": "SpeechSynthesis",
"pos": [
40,
356
],
"size": {
"0": 352.8227844238281,
"1": 95.3553237915039
},
"flags": {},
"order": 1,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"link": 12,
"widget": {
"name": "text"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": [
13,
16
],
"shape": 6,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "SpeechSynthesis"
},
"widgets_values": [
"如果您想购买工程车,您需要考虑以下几个因素:\n\n1. 预算:确定您有多少钱可以用来购买工程车。\n\n2. 用途:确定您购买工程车的目的,这将帮助您选择合适的车型和配置。\n\n3. 品牌和型号:选择一个知名品牌和适合您需求的型号。\n\n4. 购买地点:考虑在哪个地区购买,这将影响价格和可用性。\n\n5. 保养和维修:考虑车辆的保养和维修成本,以确保购买后的维护成本合理。\n\n请告诉我您需要更多的哪方面的信息,我将能更好地帮助您。"
]
},
{
"id": 8,
"type": "SpeechRecognition",
"pos": [
537,
321
],
"size": {
"0": 228.5580291748047,
"1": 239.85951232910156
},
"flags": {},
"order": 3,
"mode": 0,
"inputs": [
{
"name": "start_by",
"type": "INT",
"link": 14,
"widget": {
"name": "start_by"
}
}
],
"outputs": [
{
"name": "prompt",
"type": "STRING",
"links": [
15
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "SpeechRecognition"
},
"widgets_values": [
null,
1,
4,
null
]
},
{
"id": 4,
"type": "ChatGPTOpenAI",
"pos": [
-403,
14
],
"size": {
"0": 400,
"1": 358
},
"flags": {},
"order": 0,
"mode": 0,
"outputs": [
{
"name": "text",
"type": "STRING",
"links": [
12
],
"shape": 3,
"slot_index": 0
},
{
"name": "messages",
"type": "STRING",
"links": null,
"shape": 3
},
{
"name": "session_history",
"type": "STRING",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "ChatGPTOpenAI"
},
"widgets_values": [
null,
null,
"买",
"You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.",
"gpt-3.5-turbo",
8034,
"randomize",
1,
null
]
},
{
"id": 10,
"type": "ShowTextForGPT",
"pos": [
867,
349
],
"size": {
"0": 315,
"1": 76.00000762939453
},
"flags": {},
"order": 4,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"link": 15,
"widget": {
"name": "text"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": null,
"shape": 6
}
],
"properties": {
"Node name for S&R": "ShowTextForGPT"
},
"widgets_values": [
"工程车子什么"
]
},
{
"id": 5,
"type": "DynamicDelayProcessor",
"pos": [
496,
4
],
"size": {
"0": 400,
"1": 200
},
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "any_input",
"type": "*",
"link": 13
},
{
"name": "delay_by_text",
"type": "STRING",
"link": 16,
"widget": {
"name": "delay_by_text"
}
}
],
"outputs": [
{
"name": "output",
"type": "*",
"links": [
14
],
"shape": 3,
"slot_index": 0
}
],
"properties": {
"Node name for S&R": "DynamicDelayProcessor"
},
"widgets_values": [
1,
"",
0.5,
"enable",
1
]
}
],
"links": [
[
4,
2,
0,
5,
0,
"*"
],
[
7,
4,
0,
6,
0,
"STRING"
],
[
8,
6,
0,
5,
1,
"STRING"
],
[
9,
5,
0,
7,
0,
"INT"
],
[
12,
4,
0,
9,
0,
"STRING"
],
[
13,
9,
0,
5,
0,
"*"
],
[
14,
5,
0,
8,
0,
"INT"
],
[
15,
8,
0,
10,
0,
"STRING"
],
[
16,
9,
0,
5,
1,
"STRING"
]
],
"groups": [],
"config": {},
"extra": {},
"version": 0.4
}
File diff suppressed because it is too large Load Diff
+828
View File
@@ -0,0 +1,828 @@
{
"last_node_id": 23,
"last_link_id": 0,
"nodes": [
{
"id": 7,
"type": "ResizeImageMixlab",
"pos": [
1096.229866976564,
731.8207909156248
],
"size": {
"0": 315,
"1": 106
},
"flags": {},
"order": 0,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": null
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "ResizeImageMixlab"
},
"widgets_values": [
512,
512,
"width"
]
},
{
"id": 8,
"type": "LoadImagesFromPath",
"pos": [
1101.229866976564,
900.8207909156248
],
"size": {
"0": 315,
"1": 238
},
"flags": {},
"order": 1,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": null,
"shape": 6
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 6
},
{
"name": "STRING",
"type": "STRING",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "LoadImagesFromPath"
},
"widgets_values": [
"C:\\Users\\38957\\Documents\\GitHub\\extract-anything\\outputs",
"disable",
"enable",
0,
"disable",
null,
null
]
},
{
"id": 3,
"type": "ShowTextForGPT",
"pos": [
1786,
252
],
"size": {
"0": 315,
"1": 58
},
"flags": {},
"order": 2,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"link": null,
"widget": {
"name": "text"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": null,
"shape": 6
}
],
"properties": {
"Node name for S&R": "ShowTextForGPT"
},
"widgets_values": [
""
]
},
{
"id": 9,
"type": "TextImage",
"pos": [
1448.4608623625004,
735.0514200906249
],
"size": {
"0": 315,
"1": 198
},
"flags": {},
"order": 3,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": null,
"shape": 3
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "TextImage"
},
"widgets_values": [
"龍馬精神迎新歲",
"C:\\Users\\38957\\Documents\\ai-lab\\ComfyUI_windows_portable\\ComfyUI\\custom_nodes\\comfyui-mixlab-nodes\\assets\\王汉宗颜楷体繁.ttf",
100,
12,
"#000000",
true
]
},
{
"id": 2,
"type": "ChatGPTOpenAI",
"pos": [
1086,
235
],
"size": {
"0": 400,
"1": 358
},
"flags": {},
"order": 4,
"mode": 0,
"outputs": [
{
"name": "text",
"type": "STRING",
"links": null,
"shape": 3
},
{
"name": "messages",
"type": "STRING",
"links": null,
"shape": 3
},
{
"name": "session_history",
"type": "STRING",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "ChatGPTOpenAI"
},
"widgets_values": [
null,
null,
"",
"You are ChatGPT, a large language model trained by OpenAI. Answer as concisely as possible.",
"gpt-3.5-turbo",
0,
"randomize",
1,
null
]
},
{
"id": 16,
"type": "SpeechRecognition",
"pos": [
2892.502226325001,
724.176680771093
],
"size": {
"0": 322.593505859375,
"1": 135.22850036621094
},
"flags": {},
"order": 5,
"mode": 0,
"outputs": [
{
"name": "prompt",
"type": "STRING",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "SpeechRecognition"
},
"widgets_values": [
null,
null
]
},
{
"id": 17,
"type": "SpeechSynthesis",
"pos": [
2901.502226325001,
924.1766807710948
],
"size": {
"0": 315,
"1": 58
},
"flags": {},
"order": 6,
"mode": 0,
"inputs": [
{
"name": "text",
"type": "STRING",
"link": null,
"widget": {
"name": "text"
}
}
],
"outputs": [
{
"name": "STRING",
"type": "STRING",
"links": null,
"shape": 6
}
],
"properties": {
"Node name for S&R": "SpeechSynthesis"
},
"widgets_values": [
""
]
},
{
"id": 18,
"type": "ShowLayer",
"pos": [
2157.913220668749,
232.7887044208986
],
"size": {
"0": 315,
"1": 226
},
"flags": {},
"order": 7,
"mode": 0,
"inputs": [
{
"name": "layers",
"type": "LAYER",
"link": null
}
],
"properties": {
"Node name for S&R": "ShowLayer"
}
},
{
"id": 19,
"type": "NewLayer",
"pos": [
2494.913220668749,
232.7887044208986
],
"size": {
"0": 315,
"1": 218
},
"flags": {},
"order": 8,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": null
},
{
"name": "mask",
"type": "MASK",
"link": null
},
{
"name": "layers",
"type": "LAYER",
"link": null
}
],
"outputs": [
{
"name": "layers",
"type": "LAYER",
"links": null,
"shape": 6
}
],
"properties": {
"Node name for S&R": "NewLayer"
},
"widgets_values": [
0,
0,
512,
512,
0,
"width"
]
},
{
"id": 20,
"type": "MergeLayers",
"pos": [
2163.913220668749,
515.7887044208991
],
"size": {
"0": 315.47509765625,
"1": 69.53152465820312
},
"flags": {},
"order": 9,
"mode": 0,
"inputs": [
{
"name": "layers",
"type": "LAYER",
"link": null
},
{
"name": "image",
"type": "IMAGE",
"link": null
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "MergeLayers"
}
},
{
"id": 4,
"type": "CharacterInText",
"pos": [
1778,
386
],
"size": {
"0": 319.45068359375,
"1": 194.45606994628906
},
"flags": {},
"order": 10,
"mode": 0,
"outputs": [
{
"name": "INT",
"type": "INT",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "CharacterInText"
},
"widgets_values": [
"",
"",
1
]
},
{
"id": 21,
"type": "Note",
"pos": [
2507.333181606249,
517.1286262958992
],
"size": {
"0": 210,
"1": 58
},
"flags": {},
"order": 11,
"mode": 0,
"properties": {
"text": ""
},
"widgets_values": [
"TODO:可视化操作"
],
"color": "#323",
"bgcolor": "#535"
},
{
"id": 22,
"type": "Note",
"pos": [
1533,
347
],
"size": {
"0": 210,
"1": 58
},
"flags": {},
"order": 12,
"mode": 0,
"properties": {
"text": ""
},
"widgets_values": [
"funtion call"
],
"color": "#323",
"bgcolor": "#535"
},
{
"id": 11,
"type": "3DImage",
"pos": [
1794.6918193937495,
724.2820719460937
],
"size": {
"0": 309.2974548339844,
"1": 433.12554931640625
},
"flags": {},
"order": 13,
"mode": 0,
"inputs": [
{
"name": "material",
"type": "IMAGE",
"link": null
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": null,
"shape": 3
},
{
"name": "MASK",
"type": "MASK",
"links": null,
"shape": 3
},
{
"name": "BG_IMAGE",
"type": "IMAGE",
"links": null,
"shape": 3
},
{
"name": "MATERIAL",
"type": "IMAGE",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "3DImage"
}
},
{
"id": 13,
"type": "ScreenShare",
"pos": [
2138.691819393749,
728.2820719460937
],
"size": {
"0": 322.9458923339844,
"1": 594.0604858398438
},
"flags": {},
"order": 14,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": null,
"shape": 3
},
{
"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,
null,
null,
null,
null
]
},
{
"id": 14,
"type": "FloatingVideo",
"pos": [
2486.691819393749,
726.2820719460937
],
"size": {
"0": 302.3232421875,
"1": 303.61279296875
},
"flags": {},
"order": 15,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": null
}
],
"properties": {
"Node name for S&R": "FloatingVideo"
},
"widgets_values": [
null
]
},
{
"id": 6,
"type": "TransparentImage",
"pos": [
2496.691819393749,
1157.2820719460938
],
"size": {
"0": 315,
"1": 146
},
"flags": {},
"order": 16,
"mode": 0,
"inputs": [
{
"name": "images",
"type": "IMAGE",
"link": null
},
{
"name": "masks",
"type": "MASK",
"link": null
}
],
"outputs": [
{
"name": "file_path",
"type": "STRING",
"links": null,
"shape": 6
},
{
"name": "IMAGE",
"type": "IMAGE",
"links": null,
"shape": 6
},
{
"name": "RGBA",
"type": "RGBA",
"links": null,
"shape": 6
}
],
"properties": {
"Node name for S&R": "TransparentImage"
},
"widgets_values": [
"yes",
"yes",
"Mixlab_save"
]
},
{
"id": 12,
"type": "ImageCropByAlpha",
"pos": [
1806.6918193937495,
1240.2820719460938
],
"size": {
"0": 289.27215576171875,
"1": 46
},
"flags": {},
"order": 17,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": null
},
{
"name": "RGBA",
"type": "RGBA",
"link": null
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "ImageCropByAlpha"
}
},
{
"id": 5,
"type": "EnhanceImage",
"pos": [
1098.6918193937504,
1217.2820719460938
],
"size": {
"0": 315,
"1": 58
},
"flags": {},
"order": 18,
"mode": 0,
"inputs": [
{
"name": "image",
"type": "IMAGE",
"link": null
}
],
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": null,
"shape": 3
}
],
"properties": {
"Node name for S&R": "EnhanceImage"
},
"widgets_values": [
0.5
]
},
{
"id": 10,
"type": "SvgImage",
"pos": [
1454.6918193937504,
1074.2820719460938
],
"size": {
"0": 315,
"1": 170
},
"flags": {},
"order": 19,
"mode": 0,
"outputs": [
{
"name": "IMAGE",
"type": "IMAGE",
"links": null,
"shape": 3
},
{
"name": "layers",
"type": "LAYER",
"links": null,
"shape": 6
}
],
"properties": {
"Node name for S&R": "SvgImage"
},
"widgets_values": [
null,
null
]
}
],
"links": [],
"groups": [
{
"title": "GPT",
"bounding": [
1083,
157,
1039,
450
],
"color": "#b06634",
"font_size": 24,
"locked": false
},
{
"title": "Image",
"bounding": [
1086,
650,
1735,
682
],
"color": "#A88",
"font_size": 24,
"locked": false
},
{
"title": "Audio",
"bounding": [
2882,
650,
344,
342
],
"color": "#8A8",
"font_size": 24,
"locked": false
},
{
"title": "Layer",
"bounding": [
2148,
158,
672,
437
],
"color": "#a1309b",
"font_size": 24,
"locked": false
}
],
"config": {},
"extra": {},
"version": 0.4
}