Files

350 lines
14 KiB
Python

import numpy as np
import json
import torch
import time
import node_helpers
from io import BytesIO
from PIL import Image, ImageOps, ImageSequence, ImageFile
from nodes import CLIPTextEncode, ConditioningConcat, ConditioningSetMask
from ..apis import addImageCache
from ..protocols.photoshop import ProtocolPhotoshop
from .nodes import check_linked_in_prompt, sdppp_get_prompt_item_from_list, convert_mask_to_boundary, SDPPPOptional
import asyncio
def define_comfyui_nodes_legacy(sdppp):
def call_async_func_in_server_thread(coro, dontwait = False):
handle = {
'done': False,
'result': None,
'error': None
}
loop = sdppp.loop
async def do_call():
try:
handle['result'] = await coro
except Exception as e:
handle['error'] = e
finally:
handle['done'] = True
# 使用 run_coroutine_threadsafe 确保在 aiohttp 的线程中执行
future = asyncio.run_coroutine_threadsafe(do_call(), loop)
if not dontwait:
# 等待任务完成
future.result()
if handle['error'] is not None:
raise handle['error']
return handle['result']
return None
def parse_params(unique_id, prompt, layer_or_group, document=""):
linked_style = check_linked_in_prompt(prompt, unique_id, 'layer_or_group')
if not linked_style:
document = json.loads(document[0])
else:
document = layer_or_group[0]['document']
return linked_style, document
class GetImageFromPhotoshopLayerNode:
RETURN_TYPES = ("IMAGE", "MASK")
RETURN_NAMES = ("rgb_out", "alpha_out")
OUTPUT_IS_LIST = (True, True)
INPUT_IS_LIST = True
FUNCTION = "get_image"
CATEGORY = "SD-PPP"
@classmethod
def IS_CHANGED(self, layer_or_group, **kwargs):
if layer_or_group is not None and len(layer_or_group) > 0 and layer_or_group[0] is not None:
return json.loads(layer_or_group[0])['dirtyID']
else:
return 1
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"layer_or_group": ('LAYER', {"default": None})
},
"optional": SDPPPOptional({
"bound": ('MASK', {"default": None}),
"quality": ('FLOAT', {"default": 100.0, "min": 0.0, "max": 100.0, "step": 1.0, "forceInput": True}),
}, {
# compat combo selection type
"document": ("STRING", {"default": "", "sdppp_type": "DOCUMENT_nameid"}),
"sdppp": ("STRING", {"default": ""}),
}),
"hidden": {
"unique_id": "UNIQUE_ID",
"prompt": "PROMPT",
}
}
def get_image(self, unique_id, prompt, layer_or_group, bound="", document="", quality=100.0, **kwargs):
sdppp.has_ps_instance(throw_error=True)
linked_style, document = parse_params(unique_id, prompt, layer_or_group, document)
# dont check here, some python cannot read the data in this thread.
# if document['instance_id'] not in sdppp.ppp_instances:
# raise ValueError(f'Photoshop instance {document["instance_id"]} not found')
res_image = []
res_mask = []
startTime = time.time()
for i, item_layer in enumerate(layer_or_group):
if linked_style:
item_layer = item_layer['layer_identify']
else:
item_layer = json.loads(item_layer)
item_layer = item_layer['identify']
item_bound = sdppp_get_prompt_item_from_list(bound, i)
result = call_async_func_in_server_thread(
ProtocolPhotoshop.get_image(
instance_id=document['instance_id'],
document_identify=document['identify'],
layer_identify=item_layer,
boundary=convert_mask_to_boundary(item_bound),
quality=sdppp_get_prompt_item_from_list(quality, i)
)
)
# change to use result['pngData']. a .png data
# (output_image, output_mask) = self._load_image_from_png(
# result['pngData']
# )
output_image = self._load_image_from_jpeg(result['jpegData'])
(_, output_mask) = self._load_image_from_png(result['alphaData'])
res_image.append(output_image)
res_mask.append(output_mask)
return (res_image, res_mask,)
def _load_image_from_jpeg(self, jpeg_data):
# Load the JPEG data using PIL
image = Image.open(BytesIO(jpeg_data))
# Convert to RGB for the output image
rgb_image = image.convert("RGB")
rgb_array = np.array(rgb_image).astype(np.float32) / 255.0
output_image = torch.from_numpy(rgb_array)[None,]
return output_image
def _load_image_from_png(self, png_data):
# Load the PNG data using PIL
image = Image.open(BytesIO(png_data))
# Convert to RGB for the output image
rgb_image = image.convert("RGB")
rgb_array = np.array(rgb_image).astype(np.float32) / 255.0
output_image = torch.from_numpy(rgb_array)[None,]
# Extract alpha channel for the mask if available
if 'A' in image.getbands():
mask = np.array(image.getchannel('A')).astype(np.float32) / 255.0
output_mask = 1. - torch.from_numpy(mask).unsqueeze(0)
else:
output_mask = torch.zeros((64, 64), dtype=torch.float32, device="cpu").unsqueeze(0)
return (output_image, output_mask)
class SendImageToPhotoshopLayerNode:
RETURN_TYPES = ("LAYER",)
RETURN_NAMES = ("layer_or_group",)
OUTPUT_IS_LIST = (True, True)
OUTPUT_NODE = True
INPUT_IS_LIST = True
FUNCTION = "send_image"
CATEGORY = "SD-PPP"
@classmethod
def IS_CHANGED(self, **kwargs):
return np.random.rand()
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"images": ("IMAGE", ),
"layer_or_group": ('LAYER', {"default": None}),
},
"optional": SDPPPOptional({
"bound": ('MASK', {"default": None}),
}, {
"document": ("STRING", {"default": "", "sdppp_type": "DOCUMENT_nameid"}),
"sdppp": ("STRING", {"default": ""}),
}),
"hidden": {
"unique_id": "UNIQUE_ID",
"prompt": "PROMPT",
}
}
def send_image(self, unique_id, prompt, images, layer_or_group, bound="", document="", **kwargs):
sdppp.has_ps_instance(throw_error=True)
total_start_time = time.time()
linked_style, document = parse_params(unique_id, prompt, layer_or_group, document)
sdppp_arg = kwargs['sdppp']
sdppp_arg_item = json.loads(sdppp_get_prompt_item_from_list(sdppp_arg, 0))
is_local = sdppp_arg_item.get('isLocal', False)
# dont check here, some python cannot read the data in this thread.
# if document['instance_id'] not in sdppp.ppp_instances:
# raise ValueError(f'Photoshop instance {document["instance_id"]} not found')
params = []
# iterate layer_or_group
for tensor_index, batch in enumerate(images):
for batch_index, image in enumerate(batch):
if len(layer_or_group) == 1:
item_layer = layer_or_group[0]
else:
item_layer = layer_or_group[batch_index]
item_bound = sdppp_get_prompt_item_from_list(bound, batch_index)
if linked_style:
item_layer = item_layer['layer_identify']
else:
item_layer = json.loads(item_layer)
item_layer = item_layer['identify']
i = 255. * image.cpu().numpy()
img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
if img.mode != "RGBA":
img = img.convert("RGBA")
if (is_local):
buffer = img.tobytes('raw')
blob = {
'buffer': buffer,
'width': img.width,
'height': img.height
}
else:
buffer = BytesIO()
img.save(buffer, format="PNG")
png_data = buffer.getvalue()
blob = {
'pngData': png_data,
}
params.append({
'layer_identify': item_layer,
'boundary': convert_mask_to_boundary(item_bound),
'image_blob': blob
})
# print('before send', time.time() - total_start_time)
ret = call_async_func_in_server_thread(ProtocolPhotoshop.send_images(
instance_id=document['instance_id'],
document_identify=document['identify'],
image_blobs=[p['image_blob'] for p in params],
layer_identifies=[p['layer_identify'] for p in params],
boundaries=[p['boundary'] for p in params],
new_layer_name=sdppp_arg_item['lastOpenedWorkflow']
))
# print('final', time.time() - total_start_time)
if not 'layers' in ret:
return ([None],)
ret_layer_or_group = []
for i, ret_layer in enumerate(ret['layers']):
ret_layer_or_group.append({
'document': document,
'layer_identify': ret_layer['identify'],
'dirtyID': ret_layer['dirtyID']
})
return (ret_layer_or_group,)
class CLIPTextEncodePSRegional:
@classmethod
def INPUT_TYPES(s):
return {
"required": {
"clip": ("CLIP", {"tooltip": "The CLIP model used for encoding the text. only use the first one."}),
"texts": ("STRING", {"forceInput": True, "multiline": True, "dynamicPrompts": True, "tooltip": "The text to be encoded."}),
"masks": ("MASK", )
},
"optional": {
"strengths": ("FLOAT", {"forceInput": True, "default": 1.0, "min": 0.0, "max": 10.0, "step": 0.01}),
}
}
RETURN_TYPES = ("CONDITIONING",)
FUNCTION = "encode"
INPUT_IS_LIST = True
CATEGORY = "Photoshop"
def encode(self, clip, texts, masks, strengths=[]):
clip = clip[0]
ret = None
for i in range(len(texts)):
text = texts[i]
mask = masks[i]
if i in strengths:
strength = strengths[i]
else:
strength = 1.0
tokens = clip.tokenize(text)
output = clip.encode_from_tokens(tokens, return_pooled=True, return_dict=True)
cond = output.pop("cond")
set_area_to_bounds = False
if len(mask.shape) < 3:
mask = mask.unsqueeze(0)
c = node_helpers.conditioning_set_values([[cond, output]], {"mask": 1.0 - mask,
"set_area_to_bounds": set_area_to_bounds,
"mask_strength": strength})
if ret is None:
ret = c
else:
ret = ret + c
return (ret, )
return {
'GetImageFromPhotoshopLayerNode': GetImageFromPhotoshopLayerNode,
'SendImageToPhotoshopLayerNode': SendImageToPhotoshopLayerNode,
'CLIPTextEncodePSRegional': CLIPTextEncodePSRegional
}
# def save_images(self, images, filename_prefix="ComfyUI", prompt=None, extra_pnginfo=None):
# filename_prefix += self.prefix_append
# full_output_folder, filename, counter, subfolder, filename_prefix = folder_paths.get_save_image_path(filename_prefix, self.output_dir, images[0].shape[1], images[0].shape[0])
# results = list()
# for (batch_number, image) in enumerate(images):
# i = 255. * image.cpu().numpy()
# img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8))
# metadata = None
# if not args.disable_metadata:
# metadata = PngInfo()
# if prompt is not None:
# metadata.add_text("prompt", json.dumps(prompt))
# if extra_pnginfo is not None:
# for x in extra_pnginfo:
# metadata.add_text(x, json.dumps(extra_pnginfo[x]))
# filename_with_batch_num = filename.replace("%batch_num%", str(batch_number))
# file = f"{filename_with_batch_num}_{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
# return { "ui": { "images": results } }