This commit is contained in:
zombieyang
2024-12-24 23:15:35 +08:00
parent fb70260b34
commit 62bf546b37
21 changed files with 1382 additions and 269 deletions
+34 -17
View File
@@ -80,7 +80,7 @@ def registerSocketEvents(sdppp, sio):
return await ProtocolPhotoshop.get_image(backendInstance,
document_identify=document['identify'],
layer_identify=payload['layer_identify'],
bound_identify=payload['bound_identify'],
boundaries=payload['boundaries'],
)
# only emit by sd webui instance
@@ -88,14 +88,14 @@ def registerSocketEvents(sdppp, sio):
async def c_send_image(sid, payload={}):
if not sdppp.has_ps_instance():
return
document = payload['document']
document = payload['document']
print(sdppp.backend_instances.keys())
backendInstance = sdppp.backend_instances[document['instance_id']]
await ProtocolPhotoshop.send_images(backendInstance,
document_identify=document['identify'],
layer_identifies=payload['layer_identifies'],
bounds_identifies=payload['bounds_identifies'],
boundaries=payload['boundaries'],
image_urls=payload['image_urls']
)
@@ -108,7 +108,9 @@ def registerSocketEvents(sdppp, sio):
# only emit by photoshop instance
@sio.event
async def b_page_run(sid, payload = {}):
await sdppp.sio.emit('b_page_run', to=payload['sid'])
to_sid = payload['sid']
del payload['sid']
await sdppp.sio.emit('b_page_run', payload, to=to_sid)
@sio.event
async def b_workflow_action(sid, payload = {}):
@@ -118,14 +120,25 @@ def registerSocketEvents(sdppp, sio):
result = await sdppp.sio.call('b_workflow_action', payload, to=payload['sid'])
return result
@sio.event
async def b_set_widget_value(sid, payload = {}):
if len(sdppp.page_instances) == 0:
return {"error": "Please connect at least one page instance"}
result = await sdppp.sio.call('b_set_widget_value', payload, to=payload['sid'])
return result
@sio.event
async def b_flush_data(sid, payload = {}):
store = sdppp.backend_instances[sid].store
if store.patch_version_acceptable(payload['fromVersion']):
store.patch_data(payload['operations'], payload['fromVersion'])
else:
result = await sio.call('s_request_data', {}, to=sid)
sdppp.backend_instances[sid].store.sync_data(result['data'], result['version'])
try:
store = sdppp.backend_instances[sid].store
if store.patch_version_acceptable(payload['fromVersion']):
store.patch_data(payload['operations'], payload['fromVersion'])
else:
result = await sio.call('s_request_data', {}, to=sid)
sdppp.backend_instances[sid].store.sync_data(result['data'], result['version'])
except Exception as e:
return {"error": str(e)}
payload['sid'] = sid
if len(sdppp.page_instances):
@@ -133,12 +146,16 @@ def registerSocketEvents(sdppp, sio):
@sio.event
async def c_flush_data(sid, payload = {}):
store = sdppp.page_instances[sid].store
if store.patch_version_acceptable(payload['fromVersion']):
store.patch_data(payload['operations'], payload['fromVersion'])
else:
result = await sio.call('s_request_data', {}, to=sid)
sdppp.page_instances[sid].store.sync_data(result['data'], result['version'])
try:
store = sdppp.page_instances[sid].store
if store.patch_version_acceptable(payload['fromVersion']):
store.patch_data(payload['operations'], payload['fromVersion'])
else:
result = await sio.call('s_request_data', {}, to=sid)
sdppp.page_instances[sid].store.sync_data(result['data'], result['version'])
except Exception as e:
print('=============error============', e)
return {"error": str(e)}
payload['sid'] = sid
if len(sdppp.backend_instances):
+114 -49
View File
@@ -3,7 +3,23 @@ import time
import torch
import json
from ..protocols.photoshop import ProtocolPhotoshop
from PIL import Image, ImageOps, ImageSequence, ImageFile
from PIL import Image, ImageOps, ImageSequence, ImageFile, ImageDraw
def sdppp_is_changed(sdppp, sdppp_arg, document_arg, key = 'canvasStateID'):
document_instance_id = None
try:
sdppp_values = json.loads(sdppp_arg)
if document_arg != '' and ('document' not in sdppp_values or sdppp_values['document'] == ''):
document = json.loads(document_arg)
document_instance_id = document['instance_id']
else:
document_instance_id = sdppp_values['document']['instance_id']
return sdppp.backend_instances[document_instance_id].store.data[key]
except Exception as e:
# print('=============error============', e)
# print(sdppp_arg)
# print(document_arg)
return np.random.rand()
# def SDPPPOptional(visible_dict, hidden_dict):
# visible_dict.__contains__ = lambda key: key in visible_dict.keys() or key in hidden_dict.keys()
@@ -28,7 +44,6 @@ class SDPPPOptional(dict):
return self.optional_dict[key]
def check_linked_in_prompt(prompt, unique_id, name):
node_prompt = prompt[0][unique_id[0]]
return isinstance(node_prompt['inputs'][name], list)
@@ -46,12 +61,42 @@ def sdppp_get_prompt_item_from_list(l, index):
else:
return l[index]
def define_comfyui_nodes(sdpppServer):
def validate_sdppp():
if not sdpppServer.has_ps_instance():
return 'Photoshop is not connected'
return True
def convert_boundary_to_mask(boundary):
left = boundary['left']
top = boundary['top']
right = boundary['right']
bottom = boundary['bottom']
width = boundary['width']
height = boundary['height']
image = Image.new('L', (width + left + right, height + top + bottom), 0)
draw = ImageDraw.Draw(image)
draw.rectangle((left, top, left + width, top + height), fill=255)
mask = np.array(image.getchannel('L')).astype(np.float32) / 255.0
mask = torch.from_numpy(mask)
output_mask = mask.unsqueeze(0)
return output_mask
def convert_mask_to_boundary(mask):
if mask is None or mask == '':
return None
mask = mask.squeeze(0).numpy()
mask = (mask * 255).astype(np.uint8)
mask = Image.fromarray(mask)
bbox = mask.getbbox()
return {
'left': bbox[0],
'top': bbox[1],
'width': bbox[2] - bbox[0],
'height': bbox[3] - bbox[1],
'right': mask.width - bbox[2],
'bottom': mask.height - bbox[3],
}
def define_comfyui_nodes(sdpppServer):
def call_async_func_in_server_thread(coro, dontwait = False):
handle = {
'done': False,
@@ -86,7 +131,9 @@ def define_comfyui_nodes(sdpppServer):
@classmethod
def IS_CHANGED(self, **kwargs):
return np.random.rand()
sdppp_arg = kwargs['sdppp']
return sdppp_is_changed(sdppp, sdppp_arg, '')
@classmethod
def INPUT_TYPES(cls):
@@ -97,10 +144,16 @@ def define_comfyui_nodes(sdpppServer):
}
def action(self, layer_info):
return (layer_info['opacity'], layer_info['bound_left'], layer_info['bound_top'], layer_info['bound_width'], layer_info['bound_height'])
return (
layer_info['opacity'],
layer_info['boundary']['left'],
layer_info['boundary']['top'],
layer_info['boundary']['width'],
layer_info['boundary']['height']
)
class GetDocumentNode:
RETURN_TYPES = ("DOCUMENT", "BOUND", "BOUND")
RETURN_TYPES = ("DOCUMENT", "MASK", "MASK")
RETURN_NAMES = ("document", "document boundary", "selection boundary")
FUNCTION = "action"
CATEGORY = "SD-PPP"
@@ -113,19 +166,34 @@ def define_comfyui_nodes(sdpppServer):
}
}
def action(self, document_name):
def action(self, document_name, **kwargs):
sdpppServer.has_ps_instance(throw_error=True)
document = json.loads(document_name)
return (document, '### The Canvas ###', '### Selection ###')
result = call_async_func_in_server_thread(
ProtocolPhotoshop.get_document_info(
sdpppServer.backend_instances[document['instance_id']],
document['identify']
)
)
return (
document,
convert_boundary_to_mask(result['document_boundary']),
convert_boundary_to_mask(result['selection_boundary'])
)
class GetLayerNode:
RETURN_TYPES = ("LAYER", "BOUND", "LAYER_INFO")
RETURN_TYPES = ("LAYER", "MASK", "LAYER_INFO")
RETURN_NAMES = ("layer_or_group", "layer boundary", "layer_info")
FUNCTION = "action"
CATEGORY = "SD-PPP"
@classmethod
def IS_CHANGED(self, **kwargs):
return np.random.rand()
sdppp_arg = kwargs['sdppp']
document_arg = kwargs['document']
return sdppp_is_changed(sdpppServer, sdppp_arg, document_arg)
@classmethod
def INPUT_TYPES(cls):
@@ -140,8 +208,8 @@ def define_comfyui_nodes(sdpppServer):
}
def action(self, document, layer_or_group, **kwargs):
if validate_sdppp() is not True:
raise ValueError('Photoshop is not connected')
sdpppServer.has_ps_instance(throw_error=True)
result = call_async_func_in_server_thread(
ProtocolPhotoshop.get_layer_info(
sdpppServer.backend_instances[document['instance_id']],
@@ -153,11 +221,11 @@ def define_comfyui_nodes(sdpppServer):
return ({
"document": document,
"layer_identify": result['identify']
}, result['identify'], result)
}, convert_boundary_to_mask(result['boundary']), result)
class GetLayersInGroupNode:
RETURN_TYPES = ("LAYER", "BOUND", "LAYER_INFO")
RETURN_NAMES = ("layer_or_group", "layer_bound", "layer_info")
RETURN_TYPES = ("LAYER", "MASK", "LAYER_INFO")
RETURN_NAMES = ("layer_or_group", "layer_boundary", "layer_info")
OUTPUT_IS_LIST = (True, True, True)
INPUT_IS_LIST = True
FUNCTION = "action"
@@ -165,7 +233,9 @@ def define_comfyui_nodes(sdpppServer):
@classmethod
def IS_CHANGED(self, **kwargs):
return np.random.rand()
sdppp_arg = kwargs['sdppp'][0]
document_arg = ''
return sdppp_is_changed(sdpppServer, sdppp_arg, document_arg)
@classmethod
def INPUT_TYPES(cls):
@@ -180,8 +250,7 @@ def define_comfyui_nodes(sdpppServer):
}
def action(self, layer_or_group, select, **kwargs):
if validate_sdppp() is not True:
raise ValueError('Photoshop is not connected')
sdpppServer.has_ps_instance(throw_error=True)
document = layer_or_group[0]['document']
layer_identifies = [item['layer_identify'] for item in layer_or_group]
@@ -196,21 +265,23 @@ def define_comfyui_nodes(sdpppServer):
)
return (
[{ "document": document, "layer_identify": item } for item in result['layer_identifies']],
result['layer_identifies'],
[convert_boundary_to_mask(item) for item in result['layer_boundaries']],
result['layer_infos']
)
class GetLinkedLayersNode:
RETURN_TYPES = ("LAYER", "BOUND", "LAYER_INFO")
RETURN_NAMES = ("layer_or_group", "layer_bound", "layer_info")
RETURN_TYPES = ("LAYER", "MASK", "LAYER_INFO")
RETURN_NAMES = ("layer_or_group", "layer_boundary", "layer_info")
OUTPUT_IS_LIST = (True, True, True)
INPUT_IS_LIST = True
FUNCTION = "action"
CATEGORY = "SD-PPP"
@classmethod
def IS_CHANGED(self, **kwargs):
return np.random.rand()
sdppp_arg = kwargs['sdppp'][0]
document_arg = ''
return sdppp_is_changed(sdpppServer, sdppp_arg, document_arg)
@classmethod
def INPUT_TYPES(cls):
@@ -225,8 +296,7 @@ def define_comfyui_nodes(sdpppServer):
}
def action(self, layer_or_group, select, **kwargs):
if validate_sdppp() is not True:
raise ValueError('Photoshop is not connected')
sdpppServer.has_ps_instance(throw_error=True)
document = layer_or_group[0]['document']
layer_identifies = [item['layer_identify'] for item in layer_or_group]
@@ -241,7 +311,7 @@ def define_comfyui_nodes(sdpppServer):
)
return (
[{ "document": document, "layer_identify": item } for item in result['layer_identifies']],
result['layer_identifies'],
[convert_boundary_to_mask(item) for item in result['layer_boundaries']],
result['layer_infos']
)
@@ -252,16 +322,10 @@ def define_comfyui_nodes(sdpppServer):
CATEGORY = "SD-PPP"
@classmethod
def VALIDATE_INPUTS(s):
return validate_sdppp()
@classmethod
def IS_CHANGED(self, sdppp, **kwargs):
document = json.loads(json.loads(sdppp)['document'])
if ('instance_id' not in document) or (document['instance_id'] not in sdpppServer.backend_instances):
return np.random.rand()
return sdpppServer.backend_instances[document['instance_id']].store.data['selectionStateID']
def IS_CHANGED(self, **kwargs):
sdppp_arg = kwargs['sdppp']
document_arg = kwargs['document']
return sdppp_is_changed(sdpppServer, sdppp_arg, document_arg, 'selectionStateID')
@classmethod
def INPUT_TYPES(cls):
@@ -270,7 +334,7 @@ def define_comfyui_nodes(sdpppServer):
"document": ("DOCUMENT", {"default": None, "sdppp_type": "DOCUMENT"}),
},
"optional": SDPPPOptional({
"bound": ('BOUND', {"default": None}),
"bound": ('MASK', {"default": None}),
}, {
"sdppp": ("STRING", {"default": ""}),
}),
@@ -281,14 +345,13 @@ def define_comfyui_nodes(sdpppServer):
}
def action(self, document, bound="", **kwargs):
if validate_sdppp() is not True:
raise ValueError('Photoshop is not connected')
sdpppServer.has_ps_instance(throw_error=True)
result = call_async_func_in_server_thread(
ProtocolPhotoshop.get_selection(
backend_instance=sdpppServer.backend_instances[document['instance_id']],
document_identify=document['identify'],
bound_identify=bound,
boundary=convert_mask_to_boundary(bound),
)
)
return self._load_mask(
@@ -335,7 +398,9 @@ def define_comfyui_nodes(sdpppServer):
@classmethod
def IS_CHANGED(self, **kwargs):
return np.random.rand()
sdppp_arg = kwargs['sdppp'][0]
document_arg = kwargs['document'][0] if 'document' in kwargs and kwargs['document'] != None else ''
return sdppp_is_changed(sdpppServer, sdppp_arg, document_arg)
@classmethod
def INPUT_TYPES(cls):
@@ -343,9 +408,10 @@ def define_comfyui_nodes(sdpppServer):
"required": {
"layer_or_group": ('LAYER', {"default": None, "sdppp_type": "LAYER"}),
},
"optional": {
"optional": SDPPPOptional({}, {
"sdppp": ("STRING", {"default": ""}),
"document": ("STRING", {"default": "", "sdppp_type": "DOCUMENT_nameid"})
},
}),
"hidden": {
"unique_id": "UNIQUE_ID",
"prompt": "PROMPT",
@@ -353,8 +419,7 @@ def define_comfyui_nodes(sdpppServer):
}
def action(self, layer_or_group, unique_id, prompt, document = None, **kwargs):
if validate_sdppp() is not True:
raise ValueError('Photoshop is not connected')
sdpppServer.has_ps_instance(throw_error=True)
linked_style = check_linked_in_prompt(prompt, unique_id, 'layer_or_group')
if not linked_style:
+23 -81
View File
@@ -8,14 +8,9 @@ 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
from .nodes import check_linked_in_prompt, sdppp_get_prompt_item_from_list, convert_mask_to_boundary, SDPPPOptional, sdppp_is_changed
def define_comfyui_nodes_legacy(sdppp):
def validate_sdppp():
if not sdppp.has_ps_instance():
return 'Photoshop is not connected'
return True
def call_async_func_in_server_thread(coro, dontwait = False):
handle = {
'done': False,
@@ -60,22 +55,10 @@ def define_comfyui_nodes_legacy(sdppp):
CATEGORY = "SD-PPP"
@classmethod
def VALIDATE_INPUTS(s):
return validate_sdppp()
@classmethod
def IS_CHANGED(self, unique_id, prompt, layer_or_group, bound="", document=""):
# 'prompt' is not valid in IS_CHANGED
# so we cant figure out if this node is using linked style or not
# just consider to support linked style here.
if not isinstance(document, list) or len(document) == 0 or not isinstance(document[0], str):
return np.random.rand()
document = json.loads(document[0])
if ('instance_id' not in document) or (document['instance_id'] not in sdppp.backend_instances):
return np.random.rand()
return sdppp.backend_instances[document['instance_id']].store.data['canvasStateID']
def IS_CHANGED(self, **kwargs):
sdppp_arg = kwargs['sdppp'][0]
document_arg = kwargs['document'][0] if 'document' in kwargs and kwargs['document'] != None else ''
return sdppp_is_changed(sdppp, sdppp_arg, document_arg)
@classmethod
def INPUT_TYPES(cls):
@@ -83,20 +66,21 @@ def define_comfyui_nodes_legacy(sdppp):
"required": {
"layer_or_group": ('LAYER', {"default": None})
},
"optional": {
"optional": SDPPPOptional({
"bound": ('MASK', {"default": None}),
}, {
# compat combo selection type
"document": ("STRING", {"default": "", "sdppp_type": "DOCUMENT_nameid"}),
"bound": ('BOUND', {"default": None}),
},
"sdppp": ("STRING", {"default": ""}),
}),
"hidden": {
"unique_id": "UNIQUE_ID",
"prompt": "PROMPT",
}
}
def get_image(self, unique_id, prompt, layer_or_group, bound="", document=""):
if validate_sdppp() is not True:
raise ValueError('Photoshop is not connected')
def get_image(self, unique_id, prompt, layer_or_group, bound="", document="", **kwargs):
sdppp.has_ps_instance(throw_error=True)
linked_style, document = parse_params(unique_id, prompt, layer_or_group, document)
if document['instance_id'] not in sdppp.backend_instances:
@@ -115,7 +99,7 @@ def define_comfyui_nodes_legacy(sdppp):
backend_instance=sdppp.backend_instances[document['instance_id']],
document_identify=document['identify'],
layer_identify=item_layer,
bound_identify=item_bound
boundary=convert_mask_to_boundary(item_bound)
)
)
(output_image, output_mask) = self._load_image(
@@ -188,10 +172,6 @@ def define_comfyui_nodes_legacy(sdppp):
CATEGORY = "SD-PPP"
OUTPUT_NODE = True
@classmethod
def VALIDATE_INPUTS(s):
return validate_sdppp()
@classmethod
def IS_CHANGED(self, **kwargs):
return np.random.rand()
@@ -203,20 +183,20 @@ def define_comfyui_nodes_legacy(sdppp):
"images": ("IMAGE", ),
"layer_or_group": ('LAYER', {"default": None}),
},
"optional": {
# compat combo selection type
"optional": SDPPPOptional({
"bound": ('MASK', {"default": None}),
}, {
"document": ("STRING", {"default": "", "sdppp_type": "DOCUMENT_nameid"}),
"bound": ('BOUND', {"default": None}),
},
"sdppp": ("STRING", {"default": ""}),
}),
"hidden": {
"unique_id": "UNIQUE_ID",
"prompt": "PROMPT",
}
}
def send_image(self, unique_id, prompt, images, layer_or_group, bound="", document=""):
if validate_sdppp() is not True:
raise ValueError('Photoshop is not connected')
def send_image(self, unique_id, prompt, images, layer_or_group, bound="", document="", **kwargs):
sdppp.has_ps_instance(throw_error=True)
linked_style, document = parse_params(unique_id, prompt, layer_or_group, document)
@@ -242,7 +222,7 @@ def define_comfyui_nodes_legacy(sdppp):
img = img.convert("RGBA")
params.append({
'layer_identify': item_layer,
'bound_identify': item_bound,
'boundary': convert_mask_to_boundary(item_bound),
'image_blob': {
'buffer': img.tobytes('raw'),
'width': img.width,
@@ -255,47 +235,11 @@ def define_comfyui_nodes_legacy(sdppp):
document_identify=document['identify'],
image_blobs=[p['image_blob'] for p in params],
layer_identifies=[p['layer_identify'] for p in params],
bounds_identifies=[p['bound_identify'] for p in params]
boundaries=[p['boundary'] for p in params]
), True)
return (None,)
class ImageTimesOpacity:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"images": ("IMAGE", ),
"opacity": ("FLOAT", {"default": 1.0, "min": 0.01, "max": 1.0, "step": 0.01}),
}
}
RETURN_TYPES = ("IMAGE",)
FUNCTION = "image_times_opacity"
CATEGORY = "Photoshop"
def image_times_opacity(self, images, opacity):
image_out = images * opacity
return (image_out,)
class MaskTimesOpacity:
@classmethod
def INPUT_TYPES(cls):
return {
"required": {
"masks": ("MASK", ),
"opacity": ("FLOAT", {"default": 1.0, "min": 0.01, "max": 1.0, "step": 0.01}),
}
}
RETURN_TYPES = ("MASK",)
FUNCTION = "mask_times_opacity"
CATEGORY = "Photoshop"
def mask_times_opacity(self, masks, opacity):
mask_out = masks * opacity
return (mask_out,)
class CLIPTextEncodePSRegional:
@classmethod
def INPUT_TYPES(s):
@@ -347,7 +291,5 @@ def define_comfyui_nodes_legacy(sdppp):
return {
'GetImageFromPhotoshopLayerNode': GetImageFromPhotoshopLayerNode,
'SendImageToPhotoshopLayerNode': SendImageToPhotoshopLayerNode,
'ImageTimesOpacity': ImageTimesOpacity,
'MaskTimesOpacity': MaskTimesOpacity,
'CLIPTextEncodePSRegional': CLIPTextEncodePSRegional
}
+13 -6
View File
@@ -11,20 +11,20 @@ async def protocol_call(backend_instance, protocol_name, data):
class ProtocolPhotoshop:
@classmethod
async def get_image(cls, backend_instance, document_identify, layer_identify, bound_identify):
async def get_image(cls, backend_instance, document_identify, layer_identify, boundary):
result = await protocol_call(backend_instance, 's_get_image', data={
'document_identify': document_identify,
'layer_identify': layer_identify,
'bound_identify': bound_identify
'boundary': boundary
})
return result
@classmethod
async def send_images(cls, backend_instance, document_identify, layer_identifies, bounds_identifies, image_urls=[], image_blobs=[]):
async def send_images(cls, backend_instance, document_identify, layer_identifies, boundaries, image_urls=[], image_blobs=[]):
result = await protocol_call(backend_instance, 's_send_images', data={
'document_identify': document_identify,
'layer_identifies': layer_identifies,
'bounds_identifies': bounds_identifies,
'boundaries': boundaries,
'image_urls': image_urls,
'image_blobs': image_blobs
})
@@ -39,10 +39,17 @@ class ProtocolPhotoshop:
return result['text']
@classmethod
async def get_selection(cls, backend_instance, document_identify, bound_identify):
async def get_selection(cls, backend_instance, document_identify, boundary):
result = await protocol_call(backend_instance, 's_get_selection', data={
'document_identify': document_identify,
'bound_identify': bound_identify
'boundary': boundary
})
return result
@classmethod
async def get_document_info(cls, backend_instance, document_identify):
result = await protocol_call(backend_instance, 's_get_document_info', data={
'document_identify': document_identify
})
return result
+14 -1
View File
@@ -1,7 +1,11 @@
import socketio
import os.path as path
from .instances import BackendInstance, PageInstance
from .apis import registerSocketEvents, registerComfyHTTPEndpoints, registerSDHTTPEndpoints
# Define projectRoot as parent directory of current file
projectRoot = path.dirname(path.dirname(__file__))
class SDPPP:
def __init__(self):
self.page_instances = dict()
@@ -26,6 +30,13 @@ class SDPPP:
registerComfyHTTPEndpoints(self, PromptServer)
self.server_type = "comfy"
# serve static
PromptServer.instance.app.router.add_static(
'/sd-ppp-static',
path.join(projectRoot, 'plugins'),
show_index=True # Enable directory listing
)
def attach_to_SD(self, app):
self.sio = socketio.AsyncServer(
async_mode='asgi',
@@ -94,10 +105,12 @@ class SDPPP:
registerSocketEvents(self, self.sio)
def has_ps_instance(self):
def has_ps_instance(self, throw_error = False):
for instance in self.backend_instances.values():
if instance.type == 'photoshop':
return True
if throw_error:
raise ValueError('no Photoshop instance found')
return False