send to specific bounds

This commit is contained in:
zombieyang
2024-08-25 01:05:48 +08:00
parent ed1ba4ab4d
commit 5fbfb01bd6
11 changed files with 133 additions and 108 deletions
+2 -1
View File
@@ -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)
})
}
+19 -12
View File
@@ -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();
+6
View File
@@ -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
+2 -2
View File
@@ -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 = {
+59 -46
View File
@@ -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()
})
)
-19
View File
@@ -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 ?
-1
View File
@@ -1,5 +1,4 @@
const path = require("path");
const CleanWebpackPlugin = require("clean-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin");
module.exports = {
+3 -2
View File
@@ -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'])
set_special_send_layer_options(data['special_send_layer_options'])
set_special_send_bound_layer_options(data['special_send_bound_layer_options'])
+20 -15
View File
@@ -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,)
+7
View File
@@ -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
+15 -10
View File
@@ -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