diff --git a/javascript/sdppp-a1111.js b/javascript/sdppp-a1111.js index b03c308..3d1ccb2 100644 --- a/javascript/sdppp-a1111.js +++ b/javascript/sdppp-a1111.js @@ -113,7 +113,8 @@ document_data: data, special_get_layer_options: SDPPPSpeicialIDManager.getSpecialLayerForGet(), special_get_bound_layer_options: SDPPPSpeicialIDManager.getSpecialLayerForGetBounds(), - special_send_layer_options: SDPPPSpeicialIDManager.getSpecialLayerForSend() + special_send_layer_options: SDPPPSpeicialIDManager.getSpecialLayerForSend(), + special_send_bound_layer_options: SDPPPSpeicialIDManager.getSpecialLayerForSendBounds() }, resolve) }) } diff --git a/javascript/sdppp-comfy.js b/javascript/sdppp-comfy.js index ee30b24..130ff89 100644 --- a/javascript/sdppp-comfy.js +++ b/javascript/sdppp-comfy.js @@ -121,8 +121,8 @@ const documentName = this.widgets[0].value this.widgets[1].options.values = !documentData[documentName] ? [] : SDPPPSpeicialIDManager.getSpecialLayerForGet().concat(documentData[documentName].layers.map(layer => layer.name)); this.widgets[2].options.values = !documentData[documentName] ? [] : SDPPPSpeicialIDManager.getSpecialLayerForGetBounds().concat(documentData[documentName].layers.map(layer => layer.name)); - if (!this.widgets[1].value || this.widgets[1].options.values.indexOf(this.widgets[1].value) == -1) this.widgets[1].value = this.widgets[1].options.values[0] - if (!this.widgets[2].value || this.widgets[2].options.values.indexOf(this.widgets[2].value) == -1) this.widgets[2].value = this.widgets[2].options.values[0] + widgetCheckContainOrReset(this.widgets[1]) + widgetCheckContainOrReset(this.widgets[2]) } const getAndRender = async function () { documentData = (await new Promise(resolve => { @@ -132,12 +132,12 @@ document_data: documentData, special_get_layer_options: SDPPPSpeicialIDManager.getSpecialLayerForGet(), special_get_bound_layer_options: SDPPPSpeicialIDManager.getSpecialLayerForGetBounds(), - special_send_layer_options: SDPPPSpeicialIDManager.getSpecialLayerForSend() + special_send_layer_options: SDPPPSpeicialIDManager.getSpecialLayerForSend(), + special_send_bound_layer_options: SDPPPSpeicialIDManager.getSpecialLayerForSendBounds() }) // TODO current document is added on PS side, that's strange this.widgets[0].options.values = Object.keys(documentData); - if (!this.widgets[0].options.values.length) this.widgets[0].value = '' - if (!this.widgets[0].value || this.widgets[0].options.values.indexOf(this.widgets[0].value) == -1) this.widgets[0].value = this.widgets[0].options.values[0]; + widgetCheckContainOrReset(this.widgets[0]) resetWidget12.call(this); handleDownloadWidgets(this); } @@ -174,10 +174,12 @@ const onSelected = nodeType.prototype.onSelected; const onMouseEnter = nodeType.prototype.onMouseEnter; const onAdded = nodeType.prototype.onAdded; - const resetWidget1 = function () { + const resetWidget12 = function () { const documentName = this.widgets[0].value this.widgets[1].options.values = !documentData[documentName] ? [] : SDPPPSpeicialIDManager.getSpecialLayerForSend().concat(documentData[documentName].layers.map(layer => layer.name)); - if (!this.widgets[1].value || this.widgets[1].options.values.indexOf(this.widgets[1].value) == -1) this.widgets[1].value = this.widgets[1].options.values[0] + this.widgets[2].options.values = !documentData[documentName] ? [] : SDPPPSpeicialIDManager.getSpecialLayerForSendBounds().concat(documentData[documentName].layers.map(layer => layer.name)); + widgetCheckContainOrReset(this.widgets[1]) + widgetCheckContainOrReset(this.widgets[2]) } const getAndRender = async function () { documentData = (await new Promise(resolve => { @@ -187,13 +189,13 @@ document_data: documentData, special_get_layer_options: SDPPPSpeicialIDManager.getSpecialLayerForGet(), special_get_bound_layer_options: SDPPPSpeicialIDManager.getSpecialLayerForGetBounds(), - special_send_layer_options: SDPPPSpeicialIDManager.getSpecialLayerForSend() + special_send_layer_options: SDPPPSpeicialIDManager.getSpecialLayerForSend(), + special_send_bound_layer_options: SDPPPSpeicialIDManager.getSpecialLayerForSendBounds() }) // TODO current document is added on PS side, that's strange this.widgets[0].options.values = Object.keys(documentData); - if (!this.widgets[0].options.values.length) this.widgets[0].value = '' - if (!this.widgets[0].value || this.widgets[0].options.values.indexOf(this.widgets[0].value) == -1) this.widgets[0].value = this.widgets[0].options.values[0]; - resetWidget1.call(this); + widgetCheckContainOrReset(this.widgets[0]) + resetWidget12.call(this); handleDownloadWidgets(this); } nodeType.prototype.onSelected = async function (...args) { @@ -207,7 +209,7 @@ nodeType.prototype.onAdded = async function () { if (onAdded) await onAdded.call(this, ...args); this.widgets[0].callback = () => { - resetWidget1.call(this); + resetWidget12.call(this); } this.widgets[1].callback = () => { if (this.widgets[0].value == SDPPPSpeicialIDManager.SPECIAL_DOCUMENT_CURRENT) { @@ -233,6 +235,11 @@ 'Send Images To Photoshop', ] + function widgetCheckContainOrReset(widget) { + if (!widget.options.values.length) widget.value = '' + if (!widget.value || widget.options.values.indexOf(widget.value) == -1) widget.value = widget.options.values[0]; + } + async function checkHistoryChanges() { try { const currentState = app.graph.serialize(); diff --git a/javascript/sdppp-util.js b/javascript/sdppp-util.js index 99d6d5a..eae3065 100644 --- a/javascript/sdppp-util.js +++ b/javascript/sdppp-util.js @@ -40,6 +40,12 @@ SpeicialIDManager.SPECIAL_LAYER_SAME_AS_LAYER ] } + static getSpecialLayerForSendBounds() { + return [ + SpeicialIDManager.SPECIAL_LAYER_USE_CANVAS, + SpeicialIDManager.SPECIAL_LAYER_USE_SELECTION + ] + } static getSpecialLayerForSend() { return [ SpeicialIDManager.SPECIAL_LAYER_NEW_LAYER diff --git a/photoshop/src/system/events/get_image.js b/photoshop/src/system/events/get_image.js index 2d3c1a3..4cceb91 100644 --- a/photoshop/src/system/events/get_image.js +++ b/photoshop/src/system/events/get_image.js @@ -80,8 +80,8 @@ export default async function getImage(serverURL, params) { if (!document) throw new Error('document not found'); const layerIdentify = params.layer_identify - const boundLayerIdentify = params.bound_layer_identify - const desireBounds = getDesiredBounds(document, boundLayerIdentify, layerIdentify); + const boundsIdentify = params.bounds_identify + const desireBounds = getDesiredBounds(document, boundsIdentify, layerIdentify); const layerID = SpeicialIDManager.getLayerID(layerIdentify); const returnData = { diff --git a/photoshop/src/system/events/send_images.js b/photoshop/src/system/events/send_images.js index f9a511d..6ec75c8 100644 --- a/photoshop/src/system/events/send_images.js +++ b/photoshop/src/system/events/send_images.js @@ -1,42 +1,21 @@ -import { app, imaging } from "photoshop"; -import { SpeicialIDManager, executeAsModalUntilSuccess, findInAllSubLayer } from '../util.js'; +import { app, imaging, core, action, constants } from "photoshop"; +import { SpeicialIDManager, findInAllSubLayer } from '../util.js'; import Jimp from "../library/jimp.min"; import Model from "../model.js"; -function autocrop(jimp) { - let minX = jimp.bitmap.width - 1; - let minY = jimp.bitmap.height - 1; - let maxX = 0; - let maxY = 0; - - jimp.scan(0, 0, jimp.bitmap.width, jimp.bitmap.height, function (x, y, idx) { - const alpha = this.bitmap.data[idx + 3]; - if (alpha !== 0) { - minX = Math.min(minX, x); - minY = Math.min(minY, y); - maxX = Math.max(maxX, x); - maxY = Math.max(maxY, y); - } - }); - - const width = maxX - minX + 1; - const height = maxY - minY + 1; - jimp.crop(minX, minY, width, height); - return jimp -} - export default async function sendImages(comfyURL, params) { const imageIds = params.image_ids const documentIdentify = params.document_identify const layerIdentify = params.layer_identify + const boundsIdentify = params.bounds_identify let document = documentIdentify == SpeicialIDManager.SPECIAL_DOCUMENT_CURRENT ? app.activeDocument : app.documents.find(document => document.id == SpeicialIDManager.getDocumentID(documentIdentify)) - await executeAsModalUntilSuccess(async () => { + await core.executeAsModal(async () => { try { - const jimps = await Promise.all(imageIds.map(async imageId => + const jimps = await Promise.all(imageIds.map(async imageId => await Jimp.read(comfyURL + '/sdppp_download?name=' + imageId) )) if (!document) document = await app.createDocument({ @@ -50,7 +29,7 @@ export default async function sendImages(comfyURL, params) { const formerVisibles = activeLayers.map(layer => layer.visible); await Promise.all( imageIds.map(async (imageId, index) => { - const jimp = jimps[index] + const pixelJimp = jimps[index] let layerOrGroup; let existingLayerName; let newLayerName; @@ -64,52 +43,86 @@ export default async function sendImages(comfyURL, params) { if (!layerOrGroup || (layerOrGroup.kind == "group")) { newLayerName = existingLayerName ?? 'Images ' + imageId const newLayer = await document.createLayer("pixel", { + name: newLayerName, }) if (layerOrGroup) newLayer.move(layerOrGroup, "placeInside") else newLayer.move(document.layers[0], 'placeBefore') + layerOrGroup = newLayer - layerOrGroup.selected = false; Model.instance.ignoreNextHistoryChange() } - - autocrop(jimp) let putPixelsOptions = { documentID: document.id, layerID: layerOrGroup.id, imageData: await imaging.createImageDataFromBuffer( - jimp.bitmap.data, + pixelJimp.bitmap.data, { - width: jimp.bitmap.width, - height: jimp.bitmap.height, + width: pixelJimp.bitmap.width, + height: pixelJimp.bitmap.height, components: 4, colorSpace: "RGB" } ), replace: true, } - if (!newLayerName) { - let bounds = layerOrGroup.bounds - if (bounds.left == 0 && bounds.top == 0 && bounds.right == 0 && bounds.bottom == 0) { + let bounds = null + if (boundsIdentify == SpeicialIDManager.SPECIAL_LAYER_USE_SELECTION) { + if (document.selection.bounds) { + bounds = document.selection.bounds } - else if (bounds.width != jimp.bitmap.width || bounds.height != jimp.bitmap.height) { + } else if (boundsIdentify == SpeicialIDManager.SPECIAL_LAYER_USE_CANVAS) { + bounds = { width: document.width, height: document.height }; + bounds.left = bounds.top = bounds.right = bounds.bottom = 0; + } else { + const boundLayer = SpeicialIDManager.getLayerID(boundsIdentify) + bounds = findInAllSubLayer(document, boundLayer)?.bounds + } + + if (!bounds && !newLayerName) { + let bounds = layerOrGroup.bounds + if ( + !(bounds.left == 0 && bounds.top == 0 && bounds.right == 0 && bounds.bottom == 0) && + bounds.width != pixelJimp.bitmap.width || bounds.height != pixelJimp.bitmap.height + ) { if (bounds.width <= 1 && bounds.height <= 1) { - bounds.left = jimp.bitmap.width / 2 - bounds.top = jimp.bitmap.height / 2 + bounds.left = pixelJimp.bitmap.width / 2 + bounds.top = pixelJimp.bitmap.height / 2 } let centerBounds = {} - centerBounds.left = bounds.left + (bounds.width - jimp.bitmap.width) / 2 - centerBounds.top = bounds.top + (bounds.height - jimp.bitmap.height) / 2 - centerBounds.right = bounds.left + jimp.bitmap.width - centerBounds.bottom = bounds.top + jimp.bitmap.height - centerBounds.width = jimp.bitmap.width - centerBounds.height = jimp.bitmap.height + centerBounds.left = bounds.left + (bounds.width - pixelJimp.bitmap.width) / 2 + centerBounds.top = bounds.top + (bounds.height - pixelJimp.bitmap.height) / 2 + centerBounds.right = bounds.left + pixelJimp.bitmap.width + centerBounds.bottom = bounds.top + pixelJimp.bitmap.height + centerBounds.width = pixelJimp.bitmap.width + centerBounds.height = pixelJimp.bitmap.height bounds = centerBounds - putPixelsOptions.targetBounds = bounds } } + if (bounds) { + putPixelsOptions.targetBounds = bounds + } await imaging.putPixels(putPixelsOptions) + if (bounds) { + const selectionBounds = document.selection.bounds; + let selectionData; + if (selectionBounds) { + selectionData = await imaging.getSelection({}) + await document.selection.deselect(); + } + await layerOrGroup.scale(bounds.width / pixelJimp.bitmap.width * 100, bounds.height / pixelJimp.bitmap.height * 100, constants.AnchorPosition.TOPLEFT); + if (selectionBounds) { + await imaging.putSelection({ + imageData: selectionData.imageData, + targetBounds: selectionData.sourceBounds + }) + } + } + if (newLayerName) { + layerOrGroup.selected = false; + } + Model.instance.ignoreNextHistoryChange() }) ) diff --git a/photoshop/src/system/util.js b/photoshop/src/system/util.js index cc5a800..df10503 100644 --- a/photoshop/src/system/util.js +++ b/photoshop/src/system/util.js @@ -1,4 +1,3 @@ -import { core } from "photoshop"; import '../../../javascript/sdppp-util' const SpeicialIDManager = globalThis.SDPPPSpeicialIDManager export { SpeicialIDManager }; @@ -99,24 +98,6 @@ export async function getLayerOrGroupAfterMerged(document, layerID) { if (!visibleOriginal) layer.visible = false return [mergedLayer, true]; } -const executeAsModal = core.executeAsModal; - -export async function executeAsModalUntilSuccess(...args) { - let result; - let failed = true; - while(failed) { - try { - result = await executeAsModal(...args); - failed = false; - } catch (e) { - if (e.number != 9) { - failed = false; // This case is hit if the targetFunction throws an exception - } - } - await new Promise(r => setTimeout(r, 200)); - } - return result; -} export function getDesiredBounds(document, boundLayerIdentify, layerIdentify) { const boundsLayerID = boundLayerIdentify == SpeicialIDManager.SPECIAL_LAYER_SAME_AS_LAYER ? diff --git a/photoshop/webpack.config.js b/photoshop/webpack.config.js index 83a974d..b95133b 100644 --- a/photoshop/webpack.config.js +++ b/photoshop/webpack.config.js @@ -1,5 +1,4 @@ const path = require("path"); -const CleanWebpackPlugin = require("clean-webpack-plugin"); const CopyPlugin = require("copy-webpack-plugin"); module.exports = { diff --git a/sdppp_python/apis.py b/sdppp_python/apis.py index 379da51..79c79fa 100644 --- a/sdppp_python/apis.py +++ b/sdppp_python/apis.py @@ -6,7 +6,7 @@ except ImportError: print("SD-PPP: fastapi not found") try: from aiohttp import web - from .data import set_sd_document_data, set_special_get_bound_layer_options, set_special_get_layer_options, set_special_send_layer_options + from .data import set_sd_document_data, set_special_get_bound_layer_options, set_special_get_layer_options, set_special_send_layer_options, set_special_send_bound_layer_options except ImportError: print("SD-PPP: aiohttp not found") @@ -181,4 +181,5 @@ def registerSocketEvents(sdppp, sio): set_sd_document_data(data['document_data']) set_special_get_layer_options(data['special_get_layer_options']) set_special_get_bound_layer_options(data['special_get_bound_layer_options']) - set_special_send_layer_options(data['special_send_layer_options']) \ No newline at end of file + set_special_send_layer_options(data['special_send_layer_options']) + set_special_send_bound_layer_options(data['special_send_bound_layer_options']) \ No newline at end of file diff --git a/sdppp_python/comfy_nodes.py b/sdppp_python/comfy_nodes.py index e3987c1..552a383 100644 --- a/sdppp_python/comfy_nodes.py +++ b/sdppp_python/comfy_nodes.py @@ -3,7 +3,7 @@ import time from nodes import LoadImage from PIL import Image from .apis import addImageCache -from .data import get_sd_document_data, get_special_get_bound_layer_options, get_special_get_layer_options, get_special_send_layer_options +from .data import get_sd_document_data, get_special_get_bound_layer_options, get_special_get_layer_options, get_special_send_layer_options, get_special_send_bound_layer_options def define_comfyui_nodes(sdppp): def validate_sdppp(): @@ -45,17 +45,17 @@ def define_comfyui_nodes(sdppp): return validate_sdppp() @classmethod - def IS_CHANGED(self, layer_or_group, use_layer_bounds): + def IS_CHANGED(self, layer_or_group, bounds): if not sdppp.has_ps_instance(): return np.random.rand() else: photoshopInstance = sdppp.get_ps_instance() is_changed, history_state_id = call_async_func_in_server_thread( - photoshopInstance.check_layer_bounds_combo_changed(layer, use_layer_bounds) + photoshopInstance.check_layer_bounds_combo_changed(layer, bounds) ) if is_changed and history_state_id is None: return np.random.rand() - photoshopInstance.update_comfyui_last_value(layer, use_layer_bounds, history_state_id) + photoshopInstance.update_comfyui_last_value(layer, bounds, history_state_id) return history_state_id @classmethod @@ -64,14 +64,13 @@ def define_comfyui_nodes(sdppp): document_data_keys = document_data.keys() list_of_list_of_layers = [document_data[key]['layers'] for key in document_data_keys] return { - "required": {}, - "optional": { + "required": { "document": (list(document_data_keys), {"default": None}), "layer_or_group": ([ *get_special_get_layer_options(), *(item['name'] for sublist in list_of_list_of_layers for item in sublist) ], {"default": None}), - "use_layer_bounds": ([ + "bounds": ([ *get_special_get_bound_layer_options(), *(item['name'] for sublist in list_of_list_of_layers for item in sublist) ], {"default": None}), @@ -79,17 +78,17 @@ def define_comfyui_nodes(sdppp): } RETURN_TYPES = ("IMAGE", "MASK", "FLOAT") - RETURN_NAMES = ("image_out", "mask_out", "layer_opacity") + RETURN_NAMES = ("rgb_out", "alpha_out", "layer_opacity") FUNCTION = "get_image" CATEGORY = "Photoshop" - def get_image(self, document, layer_or_group, use_layer_bounds): + def get_image(self, document, layer_or_group, bounds): if validate_sdppp() is not True: raise ValueError('Photoshop is not connected') photoshopInstance = sdppp.get_ps_instance() image_id, layer_opacity = call_async_func_in_server_thread( - photoshopInstance.get_image(document_identify=document, layer_identify=layer_or_group, bound_layer_identify=use_layer_bounds) + photoshopInstance.get_image(document_identify=document, layer_identify=layer_or_group, bounds_identify=bounds) ) loadImage = LoadImage() @@ -109,8 +108,7 @@ def define_comfyui_nodes(sdppp): document_data_keys = document_data.keys() list_of_list_of_layers = [document_data[key]['layers'] for key in document_data_keys] return { - "required": {}, - "optional": { + "required": { "images": ("IMAGE", ), # documents_options "document": (list(document_data), {"default": None}), @@ -118,6 +116,10 @@ def define_comfyui_nodes(sdppp): *get_special_send_layer_options(), *(item['name'] for sublist in list_of_list_of_layers for item in sublist) ], {"default": None}), + "bounds": ([ + *get_special_send_bound_layer_options(), + *(item['name'] for sublist in list_of_list_of_layers for item in sublist) + ], {"default": None}), } } @@ -126,19 +128,22 @@ def define_comfyui_nodes(sdppp): CATEGORY = "Photoshop" OUTPUT_NODE = True - def send_image(self, images, document, layer_or_group): + def send_image(self, images, document, layer_or_group, bounds): if validate_sdppp() is not True: raise ValueError('Photoshop is not connected') photoshopInstance = sdppp.get_ps_instance() image_ids = [] - for (batch_number, image) in enumerate(images): + for (batch_index, image) in enumerate(images): i = 255. * image.cpu().numpy() img = Image.fromarray(np.clip(i, 0, 255).astype(np.uint8)) image_ids.append(addImageCache(img)) call_async_func_in_server_thread(photoshopInstance.send_images( - image_ids=image_ids, document_identify=document, layer_identify=layer_or_group + image_ids=image_ids, + document_identify=document, + layer_identify=layer_or_group, + bounds_identify=bounds ), True) return (None,) diff --git a/sdppp_python/data.py b/sdppp_python/data.py index 404450b..c156d27 100644 --- a/sdppp_python/data.py +++ b/sdppp_python/data.py @@ -27,3 +27,10 @@ def set_special_send_layer_options(data): special_send_layer_options = data def get_special_send_layer_options(): return special_send_layer_options + +special_send_bound_layer_options = [] +def set_special_send_bound_layer_options(data): + global special_send_bound_layer_options + special_send_bound_layer_options = data +def get_special_send_bound_layer_options(): + return special_send_bound_layer_options \ No newline at end of file diff --git a/sdppp_python/photoshop_instance.py b/sdppp_python/photoshop_instance.py index 721c89e..1dbd745 100644 --- a/sdppp_python/photoshop_instance.py +++ b/sdppp_python/photoshop_instance.py @@ -8,9 +8,11 @@ class PhotoshopInstance: self.layers = [] self.reset_change_tracker() - async def get_image(self, document_identify, layer_identify, bound_layer_identify): + async def get_image(self, document_identify, layer_identify, bounds_identify): result = await self.sdppp.sio.call('s_get_image', data={ - 'document_identify': document_identify, 'layer_identify': layer_identify, 'bound_layer_identify': bound_layer_identify + 'document_identify': document_identify, + 'layer_identify': layer_identify, + 'bounds_identify': bounds_identify }, to=self.sid) if not result: return None, None @@ -21,9 +23,12 @@ class PhotoshopInstance: return result['upload_name'], result['layer_opacity'] - async def send_images(self, image_ids, document_identify, layer_identify): + async def send_images(self, image_ids, document_identify, layer_identify, bounds_identify): result = await self.sdppp.sio.call('s_send_images', data={ - 'image_ids': image_ids, 'document_identify': document_identify, 'layer_identify': layer_identify + 'image_ids': image_ids, + 'document_identify': document_identify, + 'layer_identify': layer_identify, + 'bounds_identify': bounds_identify }, to=self.sid) # await self._update_history_state_id_after_internal_change() return result @@ -49,8 +54,8 @@ class PhotoshopInstance: current_id = await self.get_active_history_state_id() return self.get_img_state_id != current_id and self.get_img_state_id < current_id - async def check_layer_bounds_combo_changed(self, layer, use_layer_bounds): - layer_bounds_combo = f"{layer}{use_layer_bounds}" + async def check_layer_bounds_combo_changed(self, layer, bounds): + layer_bounds_combo = f"{layer}{bounds}" layer_bounds_history_state_id = self.change_tracker.get(layer_bounds_combo, None) if layer_bounds_history_state_id is None: return True @@ -63,8 +68,8 @@ class PhotoshopInstance: # have to track this value because comfyui determines if the value is changed by comparing it with the last value. # can't use the real history id because sending images changes the history id, and comfyui will think the value is changed when it's not - def update_comfyui_last_value(self, layer, use_layer_bounds, value): - layer_bounds_combo = f"{layer}{use_layer_bounds}" + def update_comfyui_last_value(self, layer, bounds, value): + layer_bounds_combo = f"{layer}{bounds}" self.comfyui_last_value_tracker[layer_bounds_combo] = value # need to update history id after internal operation otherwise it might cause infinite change loop @@ -75,8 +80,8 @@ class PhotoshopInstance: self.change_tracker = {k: history_state_id for k, v in self.change_tracker.items() if v == self.get_img_state_id} self.get_img_state_id = history_state_id # it gets into a loop if get img state is not updated - async def _update_layer_bounds_history_state_id(self, layer, use_layer_bounds): - layer_bounds_combo = f"{layer}{use_layer_bounds}" + async def _update_layer_bounds_history_state_id(self, layer, bounds): + layer_bounds_combo = f"{layer}{bounds}" latest_state_id = await self.get_active_history_state_id() self.change_tracker[layer_bounds_combo] = latest_state_id return latest_state_id