Convert http to socketio, simplify by removing push from PS.

This commit is contained in:
CMonk
2024-05-13 12:46:47 +08:00
committed by zombieyang
parent 2c00dd69fd
commit dbf9a59add
10 changed files with 213 additions and 211 deletions
+1 -22
View File
@@ -46,25 +46,4 @@ def define_http_endpoints(PromptServer, sdppp):
print('=============error============', e)
return web.json_response({
'error': str(e)
})
@PromptServer.instance.routes.get("/sd-ppp/checkchanges")
async def check_changes(request):
is_changed = False
if sdppp.has_ps_instance():
is_changed = await sdppp.get_ps_instance().is_ps_history_changed()
return web.json_response({'is_changed': is_changed}, content_type='application/json')
@PromptServer.instance.routes.get("/sd-ppp/resetchanges")
async def reset_changes(request):
if sdppp.has_ps_instance():
sdppp.get_ps_instance().reset_change_tracker()
return web.json_response({}, content_type='application/json')
@PromptServer.instance.routes.get("/sd-ppp/getlayers")
async def get_layers(request):
layer_strs = []
bounds_strs = []
if sdppp.has_ps_instance():
layer_strs, bounds_strs = sdppp.get_ps_instance().get_layers()
return web.json_response({'layer_strs': layer_strs, 'bounds_strs': bounds_strs}, content_type='application/json')
})
+9 -26
View File
@@ -20,7 +20,8 @@ def define_comfyui_nodes(sdppp):
return np.random.rand()
else:
photoshopInstance = sdppp.get_ps_instance()
id, bounds_id = self.LAYER_BOUNDS_NAME_TO_ID(layer, use_layer_bounds)
id = photoshopInstance.layer_name_to_id(layer)
bounds_id = photoshopInstance.layer_name_to_id(use_layer_bounds, id)
is_changed, history_state_id = photoshopInstance.check_layer_bounds_combo_changed(id, bounds_id)
if is_changed and history_state_id is None:
return np.random.rand()
@@ -33,7 +34,8 @@ def define_comfyui_nodes(sdppp):
layer_strs = []
bounds_strs = []
else:
layer_strs, bounds_strs = sdppp.get_ps_instance().get_layers()
layer_strs = sdppp.get_ps_instance().get_base_layers()
bounds_strs = sdppp.get_ps_instance().get_bounds_layers()
return {
"required": {
@@ -41,26 +43,6 @@ def define_comfyui_nodes(sdppp):
"use_layer_bounds": (bounds_strs, {"default": bounds_strs[0] if len(bounds_strs) > 0 else None}),
}
}
@classmethod
def LAYER_BOUNDS_NAME_TO_ID(self, layer, use_layer_bounds):
id = 0
if layer == '### Use canvas ###':
id = 0
else:
layer_name_and_id_split = layer.split('(id:')
id = int(layer_name_and_id_split.pop().strip()[:-1])
bounds_id = 0
if use_layer_bounds == '### Use canvas ###':
bounds_id = 0
elif use_layer_bounds == '### Use selection ###':
bounds_id = -1
elif use_layer_bounds == '### Same as layer ###':
bounds_id = id
else:
bounds_layer_name_and_id_split = use_layer_bounds.split('(id:')
bounds_id = int(bounds_layer_name_and_id_split.pop().strip()[:-1])
return id, bounds_id
RETURN_TYPES = ("IMAGE", "MASK", "FLOAT")
RETURN_NAMES = ("image_out", "mask_out", "layer_opacity")
@@ -72,7 +54,8 @@ def define_comfyui_nodes(sdppp):
raise ValueError('Photoshop is not connected')
photoshopInstance = sdppp.get_ps_instance()
id, bounds_id = self.LAYER_BOUNDS_NAME_TO_ID(layer, use_layer_bounds)
id = photoshopInstance.layer_name_to_id(layer)
bounds_id = photoshopInstance.layer_name_to_id(use_layer_bounds, id)
image_id, layer_opacity = photoshopInstance.get_image_from_remote(layer_id=id, bounds_id=bounds_id)
loadImage = LoadImage()
(output_image, output_mask) = loadImage.load_image(image_id)
@@ -89,8 +72,7 @@ def define_comfyui_nodes(sdppp):
if validate_sdppp() is not True:
layer_strs = []
else:
layer_strs, b = sdppp.get_ps_instance().get_layers()
layer_strs.insert(0, '### New Layer ###')
layer_strs = sdppp.get_ps_instance().get_set_layers()
return {
"required": {
@@ -110,7 +92,8 @@ def define_comfyui_nodes(sdppp):
photoshopInstance = sdppp.get_ps_instance()
ret = cache_images(images)
photoshopInstance.send_images_to_remote(image_ids=ret, layer_name=layer)
layer_id = photoshopInstance.layer_name_to_id(layer)
photoshopInstance.send_images_to_remote(image_ids=ret, layer_id=layer_id)
return (None,)
+60 -36
View File
@@ -1,4 +1,18 @@
import time
class PhotoshopInstance:
SPECIAL_LAYER_NEW_LAYER = '### New Layer ###'
SPECIAL_LAYER_USE_CANVAS = '### Use Canvas ###'
SPECIAL_LAYER_USE_SELECTION = '### Use Selection ###'
SPECIAL_LAYER_SAME_AS_LAYER = '### Same as Layer ###'
SPECIAL_LAYER_NAME_TO_ID = {
SPECIAL_LAYER_USE_CANVAS: 0,
SPECIAL_LAYER_USE_SELECTION: -1,
SPECIAL_LAYER_NEW_LAYER: -2,
SPECIAL_LAYER_SAME_AS_LAYER: -3
}
def __init__(self, sdppp, sid):
self.sdppp = sdppp
self.sid = sid
@@ -10,39 +24,61 @@ class PhotoshopInstance:
'done': False,
'result': None
}
self.sdppp.onNextTick(fn, handle)
while not handle['done']:
self.sdppp.onNextTick(fn, handle, self.sid)
start_time = time.time()
while not handle['done'] and self.sdppp.check_state_true(self.sid) and time.time() - start_time < 15:
pass
return handle['result']
def get_layers(self):
layer_strs = []
bounds_strs = []
layer_strs = list(map(lambda layer: f"{layer['name']} (id:{layer['id']})", self.layers))
layer_strs.insert(0, '### Use canvas ###')
return list(map(lambda layer: f"{layer['name']} (id:{layer['id']})", self.layers))
def get_base_layers(self):
raw_layer_strs = self.get_layers()
layer_strs = list(raw_layer_strs)
layer_strs.insert(0, self.SPECIAL_LAYER_USE_CANVAS)
return layer_strs
def get_bounds_layers(self):
bounds_strs = list(self.get_layers())
bounds_strs.insert(0, self.SPECIAL_LAYER_USE_CANVAS)
bounds_strs.insert(1, self.SPECIAL_LAYER_USE_SELECTION)
bounds_strs.insert(1, self.SPECIAL_LAYER_SAME_AS_LAYER)
return bounds_strs
bounds_strs = list(layer_strs)
bounds_strs.insert(1, '### Use selection ###')
bounds_strs.insert(1, '### Same as layer ###')
return layer_strs, bounds_strs
def get_set_layers(self):
raw_layer_strs = self.get_layers()
set_layer_strs = list(raw_layer_strs)
set_layer_strs.insert(0, self.SPECIAL_LAYER_NEW_LAYER)
return set_layer_strs
def layer_name_to_id(self, layer_name, refrence_id=None):
id = 0
if layer_name == self.SPECIAL_LAYER_SAME_AS_LAYER:
id = refrence_id
elif self.SPECIAL_LAYER_NAME_TO_ID.get(layer_name, None) is not None:
id = self.SPECIAL_LAYER_NAME_TO_ID[layer_name]
else:
layer_name_and_id_split = layer_name.split('(id:')
id = int(layer_name_and_id_split.pop().strip()[:-1])
return id
# for nodes call
def get_image_from_remote(self, layer_id, bounds_id=False):
async def _get_image(sio):
return await sio.call('get_image', data={'layer_id': layer_id, 'use_layer_bounds': bounds_id}, to=self.sid)
result = self._run_in_next_server_event(_get_image)
if not result:
return None, None
history_state_id = self._update_layer_bounds_history_state_id(layer_id, bounds_id)
self._update_history_state_id_after_internal_change(history_state_id)
return result['upload_name'], result['layer_opacity']
# for nodes call
def send_images_to_remote(self, image_ids, layer_name=""):
def send_images_to_remote(self, image_ids, layer_id=""):
async def _send_images(sio):
return await sio.call('send_images', data={'image_ids': image_ids, 'layer_name': layer_name}, to=self.sid)
return await sio.call('send_images', data={'image_ids': image_ids, 'layer_id': layer_id}, to=self.sid)
result = self._run_in_next_server_event(_send_images)
self._update_history_state_id_after_internal_change()
return result
@@ -50,14 +86,11 @@ class PhotoshopInstance:
# for nodes call
def get_active_history_state_id_from_remote(self):
async def get_active_history_state_id_from_remote(sio):
return await sio.call('get_active_history_state_id', data={}, to=self.sid)
res = await sio.call('get_active_history_state_id', data={}, to=self.sid)
return res
result = self._run_in_next_server_event(get_active_history_state_id_from_remote)
id = result.get('history_state_id', None)
return id
# for apis call
async def async_get_active_history_state_id_from_remote(self):
result = await self.sdppp.sio.call('get_active_history_state_id', data={}, to=self.sid)
if result is None:
return 0
id = result.get('history_state_id', None)
return id
@@ -65,19 +98,14 @@ class PhotoshopInstance:
# ===
# for api calls and initialize
def reset_change_tracker(self):
self.push_data = {}
self.get_img_state_id = 0
self.last_get_img_id = None
self.change_tracker = {}
self.comfyui_last_value_tracker = {}
# for apis calls
async def is_ps_history_changed(self):
push_state_id = await self.async_get_active_history_state_id_from_remote()
if push_state_id is not None:
return self.get_img_state_id < push_state_id
current_id = await self.get_active_history_state_id_from_remote()
return self.get_img_state_id != current_id
def is_ps_history_changed(self):
current_id = self.get_active_history_state_id_from_remote()
return self.get_img_state_id != current_id and self.get_img_state_id < current_id
# for nodes calls
def check_layer_bounds_combo_changed(self, layer, use_layer_bounds):
@@ -85,7 +113,7 @@ class PhotoshopInstance:
layer_bounds_history_state_id = self.change_tracker.get(layer_bounds_combo, None)
if layer_bounds_history_state_id is None:
return True
latest_state_id = self._get_push_history_state_id() or self.get_active_history_state_id_from_remote()
latest_state_id = self.get_active_history_state_id_from_remote()
if latest_state_id > layer_bounds_history_state_id:
return True, latest_state_id
#didn't change, use last value
@@ -113,8 +141,4 @@ class PhotoshopInstance:
layer_bounds_combo = f"{layer}{use_layer_bounds}"
latest_state_id = self.get_active_history_state_id_from_remote()
self.change_tracker[layer_bounds_combo] = latest_state_id
return latest_state_id
# for nodes call
def _get_push_history_state_id(self):
return self.push_data.get('history_state_id', None)
return latest_state_id
+45 -13
View File
@@ -3,6 +3,7 @@ import json
import asyncio
from socketio import exceptions
from .photoshop_instance import PhotoshopInstance
empty_queue = []
class SDPPP:
def __init__(self, PromptServer):
@@ -16,18 +17,25 @@ class SDPPP:
self.loop = PromptServer.instance.loop
self.onNextTickQueue = []
self.onNextTickQueue = {}
def has_ps_instance(self):
return len(self.photoshop_instances) > 0
def get_ps_instance(self, sid = None):
def get_ps_instance(self, sid = None) -> PhotoshopInstance:
if len(self.photoshop_instances) == 0:
return None
if sid is None:
sid = list(self.photoshop_instances.keys())[0]
return self.photoshop_instances[sid]
def onNextTick(self, fn, handle):
self.onNextTickQueue.append((fn, handle))
def onNextTick(self, fn, handle, sid):
if sid not in self.onNextTickQueue:
self.onNextTickQueue[sid] = []
self.onNextTickQueue[sid].append((fn, handle))
def check_state_true(self, sid):
return self.state.get(sid, False)
def registerSocketListeners(self):
sio = self.sio
@@ -56,10 +64,15 @@ class SDPPP:
while True:
if (self.state[sid] is False):
break
while len(self.onNextTickQueue) > 0:
item = self.onNextTickQueue.pop(0)
item[1]['result'] = await item[0](self.sio)
item[1]['done'] = True
sid_queue = self.onNextTickQueue.get(sid, empty_queue)
while len(sid_queue) > 0:
try:
item = sid_queue.pop(0)
item[1]['result'] = await item[0](self.sio)
except Exception as e:
pass
finally:
item[1]['done'] = True
await asyncio.sleep(0.5)
self.loop.create_task(selfEventLoop())
@@ -67,25 +80,44 @@ class SDPPP:
@sio.event
def disconnect(sid):
self.state[sid] = False
self.onNextTickQueue.pop(sid, None)
if sid in self.photoshop_instances:
self.photoshop_instances.pop(sid, None)
elif sid in self.comfyui_instances:
self.comfyui_instances.pop(sid, None)
sio.disconnect(sid)
# only emit by photoshop instance
@sio.event
async def sync_layers(sid, data):
instance = self.get_ps_instance(sid)
if instance is None:
return
instance.layers = data['layers']
layer_strs, bounds_strs = instance.get_layers()
layer_strs = instance.get_base_layers()
bounds_strs = instance.get_bounds_layers()
set_layer_strs = instance.get_set_layers()
for sid, instance in self.comfyui_instances.items():
await sio.emit('sync_layers', {
'layer_strs': layer_strs,
'bound_strs': bounds_strs
'bound_strs': bounds_strs,
'set_layer_strs': set_layer_strs,
}, to=sid)
@sio.event
def push_data(sid, data):
instance = self.get_ps_instance(sid)
instance.push_data.update(push_data)
def check_changes(sid):
instance = self.get_ps_instance()
if instance is None:
return
if not instance.is_ps_history_changed():
return
sio.emit('trigger_graph_change', to=sid)
@sio.event
def reset_changes(sid):
instance = self.get_ps_instance()
if instance is None:
return
instance.reset_change_tracker()
+24 -7
View File
@@ -4,6 +4,8 @@ import socketio from './socket.io.js'
let layerStrs = [];
let boundsStrs = [];
let setLayerStrs = [];
let socket = null;
console.log("[sd-ppp]", "Loading js extension");
app.registerExtension({
@@ -11,10 +13,9 @@ app.registerExtension({
init() {
},
async setup() {
await api.fetchApi(`/sd-ppp/resetchanges`);
setInterval(checkChanges, 1000);
const socket = socketio(location.origin, {
socket = socketio(location.origin, {
transports: ["websocket"],
path: '/sd-ppp/',
query: {
@@ -24,6 +25,7 @@ app.registerExtension({
});
socket.on('connect', () => {
socket.emit('reset_changes');
});
socket.on('disconnect', () => {
});
@@ -33,6 +35,10 @@ app.registerExtension({
socket.on('sync_layers', (data) => {
layerStrs = data.layer_strs;
boundsStrs = data.bound_strs;
setLayerStrs = data.set_layer_strs;
});
socket.on('trigger_graph_change', () => {
api.dispatchEvent(new CustomEvent("graphChanged"));
});
},
async beforeRegisterNodeDef(nodeType, nodeData, app) {
@@ -51,6 +57,21 @@ app.registerExtension({
if(onMouseEnter) await onMouseEnter.call(this, ...args);
this_handler.call(this);
}
} else if (nodeType.comfyClass === 'Send Images To Photoshop') {
const onSelected = nodeType.prototype.onSelected;
const onMouseEnter = nodeType.prototype.onMouseEnter;
let this_handler = function() {
this.widgets[0].options.values = setLayerStrs;
}
nodeType.prototype.onSelected = async function(...args) {
if(onSelected) await onSelected.call(this, ...args);
this_handler.call(this);
}
nodeType.prototype.onMouseEnter = async function(...args) {
if(onMouseEnter) await onMouseEnter.call(this, ...args);
this_handler.call(this);
}
}
}
});
@@ -58,7 +79,6 @@ app.registerExtension({
const SDPPPNodes = [
'Get Image From Photoshop Layer',
'Send Images To Photoshop',
'Send Images To Photoshop Set Layer',
]
async function checkChanges() {
await checkHistoryChanges();
@@ -70,10 +90,7 @@ async function checkHistoryChanges() {
const mode0NodeTypes = currentState.nodes.filter(node => node.mode == 0).map(node => node.type);
const containsSDPPPNodes = mode0NodeTypes.some(nodeType => SDPPPNodes.includes(nodeType));
if (!containsSDPPPNodes) return;
const res = await api.fetchApi(`/sd-ppp/checkchanges`);
const json = await res.json()
if (!json.is_changed) return;
api.dispatchEvent(new CustomEvent("graphChanged"));
socket.emit('check_changes')
} catch (e) {
console.error("[sd-ppp]", "Failed to check changes", e);
}
-9
View File
@@ -1,6 +1,5 @@
import React from "react";
import ComfyConnection from "../system/ComfyConnection";
import HistoryChecker from "../system/HistoryChecker";
import { storage } from "uxp";
export default class Main extends React.Component {
@@ -17,14 +16,6 @@ export default class Main extends React.Component {
isConnected: instance?.isConnected,
comfyURL: instance ? instance.comfyURL : ''
})
if (instance?.isConnected) {
const hc = HistoryChecker.createInstance();
hc.setChangeCallback((historyId) => {
instance.pushData({history_state_id: historyId})
});
} else {
HistoryChecker.instance?.destroy();
}
});
storage.secureStorage.getItem('comfyURL').then((value) => {
if (!value) return
+10 -15
View File
@@ -40,6 +40,7 @@ class ComfyConnection {
this.connect();
}
connect() {
console.log('connect')
if (!this.socket) {
this._createSocket();
}
@@ -52,21 +53,10 @@ class ComfyConnection {
}
}
pushData(data) {
if (!this.isConnected) {
console.error('Connection not open');
return;
}
try {
this.socket.emit('push_data', {
push_data: data,
});
} catch (e) { console.error(e) }
}
_createSocket() {
console.log('create socket')
const socket = this.socket = socketio(this.comfyURL, {
autoConnect: false,
transports: ["websocket"],
path: '/sd-ppp/',
query: {
@@ -76,17 +66,22 @@ class ComfyConnection {
});
this.interval = setInterval(() => {
if (!this.isConnected) return;
const allLayers = getAllSubLayer(app.activeDocument);
const allLayers = getAllSubLayer(app.activeDocument);
this.socket.emit('sync_layers',
{ layers: allLayers }
)
}, 3000)
socket.on('connect_error', (error) => {
console.error('connect_error', error)
if (socket.active) {
console.error('connect_error reconnecting...')
} else {
console.error('connect_error disconnected')
}
ComfyConnection._callConnectStateChange();
});
socket.on('connect', () => {
console.log('connect')
storage.secureStorage.setItem('comfyURL', this.comfyURL);
ComfyConnection._callConnectStateChange();
});
-43
View File
@@ -1,43 +0,0 @@
import {app, action} from "photoshop";
class HistoryChecker {
static instance = null;
static createInstance() {
if (HistoryChecker.instance) {
return HistoryChecker.instance;
}
return new HistoryChecker();
}
constructor() {
HistoryChecker.instance = this;
this.lastCheckId = -1;
this.changeCallback = null;
action.addNotificationListener(["historyStateChanged"], () => {
this.checkHistoryState();
});
}
destroy() {
clearInterval(this.timer);
this.timer = null;
this.changeCallback = null;
HistoryChecker.instance = null;
}
setChangeCallback(callback) {
this.changeCallback = callback;
}
checkHistoryState() {
const historyStates = app.activeDocument?.historyStates;
if (!historyStates || historyStates.length == 0) return;
const historyState = historyStates[historyStates.length - 1];
const historyId = historyState.id;
if (this.lastCheckId == historyId) return;
this.lastCheckId = historyId;
if (this.changeCallback)
this.changeCallback(historyId);
}
}
export default HistoryChecker;
+63 -39
View File
@@ -2,6 +2,17 @@ import { app, imaging } from "photoshop";
import { executeAsModalUntilSuccess} from '../util.js';
import Jimp from "../library/jimp.min";
const SPECIAL_LAYER_USE_CANVAS = '### Use Canvas ###'
const SPECIAL_LAYER_USE_SELECTION = '### Use Selection ###'
const SPECIAL_LAYER_NEW_LAYER = '### New Layer ###'
const SPECIAL_LAYER_SAME_AS_LAYER = '### Same as Layer ###'
const SPECIAL_LAYER_NAME_TO_ID = {
SPECIAL_LAYER_USE_CANVAS: 0,
SPECIAL_LAYER_USE_SELECTION: -1,
SPECIAL_LAYER_NEW_LAYER: -2,
SPECIAL_LAYER_SAME_AS_LAYER: -3
}
function autocrop(jimp) {
let minX = jimp.bitmap.width - 1;
let minY = jimp.bitmap.height - 1;
@@ -25,9 +36,8 @@ function autocrop(jimp) {
}
export default async function sendImages(comfyURL, params) {
const imageIds = params.image_ids
const layerName = params.layer_name
const layerId = params.layer_id
await Promise.all(
imageIds.map(async imageId => {
let layer;
@@ -35,58 +45,72 @@ export default async function sendImages(comfyURL, params) {
let newLayerName;
await executeAsModalUntilSuccess(async () => {
try {
if (layerName && layerName != "### New Layer ###") {
if (layerId && layerId != SPECIAL_LAYER_NAME_TO_ID[SPECIAL_LAYER_NEW_LAYER]) {
layer = await app.activeDocument.layers.find(l => l.id == layerId)
// deal with multiple images
let imageIndexSuffix = ""
console.log("imageIds.length: ", imageIds.length)
if (imageIds.length > 1){
imageIndexSuffix = ` ${imageIds.indexOf(imageId)}`
index = imageIds.indexOf(imageId)
console.log("imageIds.index: ", index)
if (index > 0)
imageIndexSuffix = ` ${index}`
}
if (imageIndexSuffix != "" && layer != null){
const layerName = layer?.name;
existingLayerName = layerName + imageIndexSuffix
console.log("existingLayerName: ", existingLayerName)
layer = await app.activeDocument.layers.find(l => l.name == existingLayerName)
}
existingLayerName = layerName + imageIndexSuffix
layer = await app.activeDocument.layers.find(l => l.name == existingLayerName)
}
// deal with new layer or id/name not found layer
if (!layer) {
newLayerName = existingLayerName ?? 'Comfy Images ' + imageId
console.log("newLayerName: ", newLayerName)
layer = await app.activeDocument.createLayer("pixel", {
name: newLayerName
})
}
const jimp = (await Jimp.read(comfyURL + '/finished_images?id=' + imageId))
autocrop(jimp)
console.log("layer name ", layer.name)
let putPixelsOptions = {
layerID: layer.id,
imageData: await imaging.createImageDataFromBuffer(
jimp.bitmap.data,
{
width: jimp.bitmap.width,
height: jimp.bitmap.height,
components: 4,
colorSpace: "RGB"
}
),
replace: true,
}
if (!newLayerName) {
let bounds = layer.bounds
if (bounds.width != jimp.bitmap.width || bounds.height != jimp.bitmap.height) {
if (bounds.width <= 1 && bounds.height <= 1) {
bounds.left = jimp.bitmap.width / 2
bounds.top = jimp.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
bounds = centerBounds
}
putPixelsOptions.targetBounds = bounds
}
await imaging.putPixels(putPixelsOptions)
} catch(e) {
console.error(e)
throw e
}
})
const jimp = (await Jimp.read(comfyURL + '/finished_images?id=' + imageId))
autocrop(jimp)
let putPixelsOptions = {
layerID: layer.id,
imageData: await imaging.createImageDataFromBuffer(
jimp.bitmap.data,
{
width: jimp.bitmap.width,
height: jimp.bitmap.height,
components: 4,
colorSpace: "RGB"
}
),
replace: true,
}
if (!newLayerName) {
let bounds = layer.bounds
if (bounds.width != jimp.bitmap.width || bounds.height != jimp.bitmap.height) {
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
bounds = centerBounds
}
putPixelsOptions.targetBounds = bounds
}
await executeAsModalUntilSuccess(async () => {
await imaging.putPixels(putPixelsOptions)
})
})
)
return {}
+1 -1
View File
@@ -57,7 +57,7 @@ export function unTrimImageData(
}
export function getAllSubLayer(layer, level = 0) {
if (!layer.layers) return [];
if (!layer?.layers) return [];
return layer.layers.reduce((ret, layer) => {
ret.push({
name: '-'.repeat(level) + layer.name,