Skip to content

Commit 1c5c3cd

Browse files
tidy(ui): organize control layers konva logic
- More comments, docstrings - Move things into saner, less-coupled locations
1 parent 3db69af commit 1c5c3cd

File tree

15 files changed

+481
-391
lines changed

15 files changed

+481
-391
lines changed

invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@ import { createSelector } from '@reduxjs/toolkit';
44
import { logger } from 'app/logging/logger';
55
import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
66
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
7+
import { BRUSH_SPACING_PCT, MAX_BRUSH_SPACING_PX, MIN_BRUSH_SPACING_PX } from 'features/controlLayers/konva/constants';
8+
import { setStageEventHandlers } from 'features/controlLayers/konva/events';
9+
import { debouncedRenderers, renderers as normalRenderers } from 'features/controlLayers/konva/renderers';
710
import {
811
$brushSize,
912
$brushSpacingPx,
@@ -15,24 +18,16 @@ import {
1518
$selectedLayerType,
1619
$shouldInvertBrushSizeScrollDirection,
1720
$tool,
18-
BRUSH_SPACING_PCT,
1921
brushSizeChanged,
2022
isRegionalGuidanceLayer,
2123
layerBboxChanged,
2224
layerTranslated,
23-
MAX_BRUSH_SPACING_PX,
24-
MIN_BRUSH_SPACING_PX,
2525
rgLayerLineAdded,
2626
rgLayerPointsAdded,
2727
rgLayerRectAdded,
2828
selectControlLayersSlice,
2929
} from 'features/controlLayers/store/controlLayersSlice';
3030
import type { AddLineArg, AddPointToLineArg, AddRectArg } from 'features/controlLayers/store/types';
31-
import {
32-
debouncedRenderers,
33-
renderers as normalRenderers,
34-
setStageEventHandlers,
35-
} from 'features/controlLayers/util/renderers';
3631
import Konva from 'konva';
3732
import type { IRect } from 'konva/lib/types';
3833
import { clamp } from 'lodash-es';

invokeai/frontend/web/src/features/controlLayers/util/bbox.ts renamed to invokeai/frontend/web/src/features/controlLayers/konva/bbox.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
11
import openBase64ImageInTab from 'common/util/openBase64ImageInTab';
22
import { imageDataToDataURL } from 'features/canvas/util/blobToDataURL';
3-
import { RG_LAYER_OBJECT_GROUP_NAME } from 'features/controlLayers/store/controlLayersSlice';
43
import Konva from 'konva';
54
import type { IRect } from 'konva/lib/types';
65
import { assert } from 'tsafe';
76

8-
const GET_CLIENT_RECT_CONFIG = { skipTransform: true };
7+
import { RG_LAYER_OBJECT_GROUP_NAME } from './naming';
98

109
type Extents = {
1110
minX: number;
@@ -14,10 +13,13 @@ type Extents = {
1413
maxY: number;
1514
};
1615

16+
const GET_CLIENT_RECT_CONFIG = { skipTransform: true };
17+
18+
//#region getImageDataBbox
1719
/**
1820
* Get the bounding box of an image.
1921
* @param imageData The ImageData object to get the bounding box of.
20-
* @returns The minimum and maximum x and y values of the image's bounding box.
22+
* @returns The minimum and maximum x and y values of the image's bounding box, or null if the image has no pixels.
2123
*/
2224
const getImageDataBbox = (imageData: ImageData): Extents | null => {
2325
const { data, width, height } = imageData;
@@ -51,7 +53,9 @@ const getImageDataBbox = (imageData: ImageData): Extents | null => {
5153

5254
return isEmpty ? null : { minX, minY, maxX, maxY };
5355
};
56+
//#endregion
5457

58+
//#region getIsolatedRGLayerClone
5559
/**
5660
* Clones a regional guidance konva layer onto an offscreen stage/canvas. This allows the pixel data for a given layer
5761
* to be captured, manipulated or analyzed without interference from other layers.
@@ -88,7 +92,9 @@ const getIsolatedRGLayerClone = (layer: Konva.Layer): { stageClone: Konva.Stage;
8892

8993
return { stageClone, layerClone };
9094
};
95+
//#endregion
9196

97+
//#region getLayerBboxPixels
9298
/**
9399
* Get the bounding box of a regional prompt konva layer. This function has special handling for regional prompt layers.
94100
* @param layer The konva layer to get the bounding box of.
@@ -137,7 +143,9 @@ export const getLayerBboxPixels = (layer: Konva.Layer, preview: boolean = false)
137143

138144
return correctedLayerBbox;
139145
};
146+
//#endregion
140147

148+
//#region getLayerBboxFast
141149
/**
142150
* Get the bounding box of a konva layer. This function is faster than `getLayerBboxPixels` but less accurate. It
143151
* should only be used when there are no eraser strokes or shapes in the layer.
@@ -153,3 +161,4 @@ export const getLayerBboxFast = (layer: Konva.Layer): IRect => {
153161
height: Math.floor(bbox.height),
154162
};
155163
};
164+
//#endregion
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/**
2+
* A transparency checker pattern image.
3+
* This is invokeai/frontend/web/public/assets/images/transparent_bg.png as a dataURL
4+
*/
5+
export const TRANSPARENCY_CHECKER_PATTERN =
6+
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABQAAAAUCAIAAAAC64paAAAEsmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4KPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iWE1QIENvcmUgNS41LjAiPgogPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4KICA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIgogICAgeG1sbnM6ZXhpZj0iaHR0cDovL25zLmFkb2JlLmNvbS9leGlmLzEuMC8iCiAgICB4bWxuczp0aWZmPSJodHRwOi8vbnMuYWRvYmUuY29tL3RpZmYvMS4wLyIKICAgIHhtbG5zOnBob3Rvc2hvcD0iaHR0cDovL25zLmFkb2JlLmNvbS9waG90b3Nob3AvMS4wLyIKICAgIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIKICAgIHhtbG5zOnhtcE1NPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvbW0vIgogICAgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIKICAgZXhpZjpQaXhlbFhEaW1lbnNpb249IjIwIgogICBleGlmOlBpeGVsWURpbWVuc2lvbj0iMjAiCiAgIGV4aWY6Q29sb3JTcGFjZT0iMSIKICAgdGlmZjpJbWFnZVdpZHRoPSIyMCIKICAgdGlmZjpJbWFnZUxlbmd0aD0iMjAiCiAgIHRpZmY6UmVzb2x1dGlvblVuaXQ9IjIiCiAgIHRpZmY6WFJlc29sdXRpb249IjMwMC8xIgogICB0aWZmOllSZXNvbHV0aW9uPSIzMDAvMSIKICAgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIKICAgcGhvdG9zaG9wOklDQ1Byb2ZpbGU9InNSR0IgSUVDNjE5NjYtMi4xIgogICB4bXA6TW9kaWZ5RGF0ZT0iMjAyNC0wNC0yM1QwODoyMDo0NysxMDowMCIKICAgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyNC0wNC0yM1QwODoyMDo0NysxMDowMCI+CiAgIDx4bXBNTTpIaXN0b3J5PgogICAgPHJkZjpTZXE+CiAgICAgPHJkZjpsaQogICAgICBzdEV2dDphY3Rpb249InByb2R1Y2VkIgogICAgICBzdEV2dDpzb2Z0d2FyZUFnZW50PSJBZmZpbml0eSBQaG90byAxLjEwLjgiCiAgICAgIHN0RXZ0OndoZW49IjIwMjQtMDQtMjNUMDg6MjA6NDcrMTA6MDAiLz4KICAgIDwvcmRmOlNlcT4KICAgPC94bXBNTTpIaXN0b3J5PgogIDwvcmRmOkRlc2NyaXB0aW9uPgogPC9yZGY6UkRGPgo8L3g6eG1wbWV0YT4KPD94cGFja2V0IGVuZD0iciI/Pn9pdVgAAAGBaUNDUHNSR0IgSUVDNjE5NjYtMi4xAAAokXWR3yuDURjHP5uJmKghFy6WxpVpqMWNMgm1tGbKr5vt3S+1d3t73y3JrXKrKHHj1wV/AbfKtVJESq53TdywXs9rakv2nJ7zfM73nOfpnOeAPZJRVMPhAzWb18NTAffC4pK7oYiDTjpw4YgqhjYeCgWpaR8P2Kx457Vq1T73rzXHE4YCtkbhMUXT88LTwsG1vGbxrnC7ko7Ghc+F+3W5oPC9pcfKXLQ4VeYvi/VIeALsbcLuVBXHqlhJ66qwvByPmikov/exXuJMZOfnJPaId2MQZooAbmaYZAI/g4zK7MfLEAOyoka+7yd/lpzkKjJrrKOzSoo0efpFLUj1hMSk6AkZGdat/v/tq5EcHipXdwag/sU033qhYQdK26b5eWyapROoe4arbCU/dwQj76JvVzTPIbRuwsV1RYvtweUWdD1pUT36I9WJ25NJeD2DlkVw3ULTcrlnv/ucPkJkQ77qBvYPoE/Ot658AxagZ8FoS/a7AAAACXBIWXMAAC4jAAAuIwF4pT92AAAAL0lEQVQ4jWM8ffo0A25gYmKCR5YJjxxBMKp5ZGhm/P//Px7pM2fO0MrmUc0jQzMAB2EIhZC3pUYAAAAASUVORK5CYII=';
7+
8+
/**
9+
* The color of a bounding box stroke when its object is selected.
10+
*/
11+
export const BBOX_SELECTED_STROKE = 'rgba(78, 190, 255, 1)';
12+
13+
/**
14+
* The inner border color for the brush preview.
15+
*/
16+
export const BRUSH_BORDER_INNER_COLOR = 'rgba(0,0,0,1)';
17+
18+
/**
19+
* The outer border color for the brush preview.
20+
*/
21+
export const BRUSH_BORDER_OUTER_COLOR = 'rgba(255,255,255,0.8)';
22+
23+
/**
24+
* The target spacing of individual points of brush strokes, as a percentage of the brush size.
25+
*/
26+
export const BRUSH_SPACING_PCT = 10;
27+
28+
/**
29+
* The minimum brush spacing in pixels.
30+
*/
31+
export const MIN_BRUSH_SPACING_PX = 5;
32+
33+
/**
34+
* The maximum brush spacing in pixels.
35+
*/
36+
export const MAX_BRUSH_SPACING_PX = 15;
Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
import { calculateNewBrushSize } from 'features/canvas/hooks/useCanvasZoom';
2+
import {
3+
getIsFocused,
4+
getIsMouseDown,
5+
getScaledFlooredCursorPosition,
6+
snapPosToStage,
7+
} from 'features/controlLayers/konva/util';
8+
import type { AddLineArg, AddPointToLineArg, AddRectArg, Layer, Tool } from 'features/controlLayers/store/types';
9+
import type Konva from 'konva';
10+
import type { Vector2d } from 'konva/lib/types';
11+
import type { WritableAtom } from 'nanostores';
12+
13+
import { TOOL_PREVIEW_LAYER_ID } from './naming';
14+
15+
type SetStageEventHandlersArg = {
16+
stage: Konva.Stage;
17+
$tool: WritableAtom<Tool>;
18+
$isDrawing: WritableAtom<boolean>;
19+
$lastMouseDownPos: WritableAtom<Vector2d | null>;
20+
$lastCursorPos: WritableAtom<Vector2d | null>;
21+
$lastAddedPoint: WritableAtom<Vector2d | null>;
22+
$brushSize: WritableAtom<number>;
23+
$brushSpacingPx: WritableAtom<number>;
24+
$selectedLayerId: WritableAtom<string | null>;
25+
$selectedLayerType: WritableAtom<Layer['type'] | null>;
26+
$shouldInvertBrushSizeScrollDirection: WritableAtom<boolean>;
27+
onRGLayerLineAdded: (arg: AddLineArg) => void;
28+
onRGLayerPointAddedToLine: (arg: AddPointToLineArg) => void;
29+
onRGLayerRectAdded: (arg: AddRectArg) => void;
30+
onBrushSizeChanged: (size: number) => void;
31+
};
32+
33+
const syncCursorPos = (stage: Konva.Stage, $lastCursorPos: WritableAtom<Vector2d | null>) => {
34+
const pos = getScaledFlooredCursorPosition(stage);
35+
if (!pos) {
36+
return null;
37+
}
38+
$lastCursorPos.set(pos);
39+
return pos;
40+
};
41+
42+
export const setStageEventHandlers = ({
43+
stage,
44+
$tool,
45+
$isDrawing,
46+
$lastMouseDownPos,
47+
$lastCursorPos,
48+
$lastAddedPoint,
49+
$brushSize,
50+
$brushSpacingPx,
51+
$selectedLayerId,
52+
$selectedLayerType,
53+
$shouldInvertBrushSizeScrollDirection,
54+
onRGLayerLineAdded,
55+
onRGLayerPointAddedToLine,
56+
onRGLayerRectAdded,
57+
onBrushSizeChanged,
58+
}: SetStageEventHandlersArg): (() => void) => {
59+
stage.on('mouseenter', (e) => {
60+
const stage = e.target.getStage();
61+
if (!stage) {
62+
return;
63+
}
64+
const tool = $tool.get();
65+
stage.findOne<Konva.Layer>(`#${TOOL_PREVIEW_LAYER_ID}`)?.visible(tool === 'brush' || tool === 'eraser');
66+
});
67+
68+
stage.on('mousedown', (e) => {
69+
const stage = e.target.getStage();
70+
if (!stage) {
71+
return;
72+
}
73+
const tool = $tool.get();
74+
const pos = syncCursorPos(stage, $lastCursorPos);
75+
const selectedLayerId = $selectedLayerId.get();
76+
const selectedLayerType = $selectedLayerType.get();
77+
if (!pos || !selectedLayerId || selectedLayerType !== 'regional_guidance_layer') {
78+
return;
79+
}
80+
if (tool === 'brush' || tool === 'eraser') {
81+
onRGLayerLineAdded({
82+
layerId: selectedLayerId,
83+
points: [pos.x, pos.y, pos.x, pos.y],
84+
tool,
85+
});
86+
$isDrawing.set(true);
87+
$lastMouseDownPos.set(pos);
88+
} else if (tool === 'rect') {
89+
$lastMouseDownPos.set(snapPosToStage(pos, stage));
90+
}
91+
});
92+
93+
stage.on('mouseup', (e) => {
94+
const stage = e.target.getStage();
95+
if (!stage) {
96+
return;
97+
}
98+
const pos = $lastCursorPos.get();
99+
const selectedLayerId = $selectedLayerId.get();
100+
const selectedLayerType = $selectedLayerType.get();
101+
102+
if (!pos || !selectedLayerId || selectedLayerType !== 'regional_guidance_layer') {
103+
return;
104+
}
105+
const lastPos = $lastMouseDownPos.get();
106+
const tool = $tool.get();
107+
if (lastPos && selectedLayerId && tool === 'rect') {
108+
const snappedPos = snapPosToStage(pos, stage);
109+
onRGLayerRectAdded({
110+
layerId: selectedLayerId,
111+
rect: {
112+
x: Math.min(snappedPos.x, lastPos.x),
113+
y: Math.min(snappedPos.y, lastPos.y),
114+
width: Math.abs(snappedPos.x - lastPos.x),
115+
height: Math.abs(snappedPos.y - lastPos.y),
116+
},
117+
});
118+
}
119+
$isDrawing.set(false);
120+
$lastMouseDownPos.set(null);
121+
});
122+
123+
stage.on('mousemove', (e) => {
124+
const stage = e.target.getStage();
125+
if (!stage) {
126+
return;
127+
}
128+
const tool = $tool.get();
129+
const pos = syncCursorPos(stage, $lastCursorPos);
130+
const selectedLayerId = $selectedLayerId.get();
131+
const selectedLayerType = $selectedLayerType.get();
132+
133+
stage.findOne<Konva.Layer>(`#${TOOL_PREVIEW_LAYER_ID}`)?.visible(tool === 'brush' || tool === 'eraser');
134+
135+
if (!pos || !selectedLayerId || selectedLayerType !== 'regional_guidance_layer') {
136+
return;
137+
}
138+
if (getIsFocused(stage) && getIsMouseDown(e) && (tool === 'brush' || tool === 'eraser')) {
139+
if ($isDrawing.get()) {
140+
// Continue the last line
141+
const lastAddedPoint = $lastAddedPoint.get();
142+
if (lastAddedPoint) {
143+
// Dispatching redux events impacts perf substantially - using brush spacing keeps dispatches to a reasonable number
144+
if (Math.hypot(lastAddedPoint.x - pos.x, lastAddedPoint.y - pos.y) < $brushSpacingPx.get()) {
145+
return;
146+
}
147+
}
148+
$lastAddedPoint.set({ x: pos.x, y: pos.y });
149+
onRGLayerPointAddedToLine({ layerId: selectedLayerId, point: [pos.x, pos.y] });
150+
} else {
151+
// Start a new line
152+
onRGLayerLineAdded({ layerId: selectedLayerId, points: [pos.x, pos.y, pos.x, pos.y], tool });
153+
}
154+
$isDrawing.set(true);
155+
}
156+
});
157+
158+
stage.on('mouseleave', (e) => {
159+
const stage = e.target.getStage();
160+
if (!stage) {
161+
return;
162+
}
163+
const pos = syncCursorPos(stage, $lastCursorPos);
164+
$isDrawing.set(false);
165+
$lastCursorPos.set(null);
166+
$lastMouseDownPos.set(null);
167+
const selectedLayerId = $selectedLayerId.get();
168+
const selectedLayerType = $selectedLayerType.get();
169+
const tool = $tool.get();
170+
171+
stage.findOne<Konva.Layer>(`#${TOOL_PREVIEW_LAYER_ID}`)?.visible(false);
172+
173+
if (!pos || !selectedLayerId || selectedLayerType !== 'regional_guidance_layer') {
174+
return;
175+
}
176+
if (getIsFocused(stage) && getIsMouseDown(e) && (tool === 'brush' || tool === 'eraser')) {
177+
onRGLayerPointAddedToLine({ layerId: selectedLayerId, point: [pos.x, pos.y] });
178+
}
179+
});
180+
181+
stage.on('wheel', (e) => {
182+
e.evt.preventDefault();
183+
const selectedLayerType = $selectedLayerType.get();
184+
const tool = $tool.get();
185+
if (selectedLayerType !== 'regional_guidance_layer' || (tool !== 'brush' && tool !== 'eraser')) {
186+
return;
187+
}
188+
189+
// Invert the delta if the property is set to true
190+
let delta = e.evt.deltaY;
191+
if ($shouldInvertBrushSizeScrollDirection.get()) {
192+
delta = -delta;
193+
}
194+
195+
if (e.evt.ctrlKey || e.evt.metaKey) {
196+
onBrushSizeChanged(calculateNewBrushSize($brushSize.get(), delta));
197+
}
198+
});
199+
200+
return () => stage.off('mousedown mouseup mousemove mouseenter mouseleave wheel');
201+
};
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* Konva filters
3+
* https://konvajs.org/docs/filters/Custom_Filter.html
4+
*/
5+
6+
/**
7+
* Calculates the lightness (HSL) of a given pixel and sets the alpha channel to that value.
8+
* This is useful for edge maps and other masks, to make the black areas transparent.
9+
* @param imageData The image data to apply the filter to
10+
*/
11+
export const LightnessToAlphaFilter = (imageData: ImageData): void => {
12+
const len = imageData.data.length / 4;
13+
for (let i = 0; i < len; i++) {
14+
const r = imageData.data[i * 4 + 0] as number;
15+
const g = imageData.data[i * 4 + 1] as number;
16+
const b = imageData.data[i * 4 + 2] as number;
17+
const cMin = Math.min(r, g, b);
18+
const cMax = Math.max(r, g, b);
19+
imageData.data[i * 4 + 3] = (cMin + cMax) / 2;
20+
}
21+
};
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/**
2+
* This file contains IDs, names, and ID getters for konva layers and objects.
3+
*/
4+
5+
// IDs for singleton Konva layers and objects
6+
export const TOOL_PREVIEW_LAYER_ID = 'tool_preview_layer';
7+
export const TOOL_PREVIEW_BRUSH_GROUP_ID = 'tool_preview_layer.brush_group';
8+
export const TOOL_PREVIEW_BRUSH_FILL_ID = 'tool_preview_layer.brush_fill';
9+
export const TOOL_PREVIEW_BRUSH_BORDER_INNER_ID = 'tool_preview_layer.brush_border_inner';
10+
export const TOOL_PREVIEW_BRUSH_BORDER_OUTER_ID = 'tool_preview_layer.brush_border_outer';
11+
export const TOOL_PREVIEW_RECT_ID = 'tool_preview_layer.rect';
12+
export const BACKGROUND_LAYER_ID = 'background_layer';
13+
export const BACKGROUND_RECT_ID = 'background_layer.rect';
14+
export const NO_LAYERS_MESSAGE_LAYER_ID = 'no_layers_message';
15+
16+
// Names for Konva layers and objects (comparable to CSS classes)
17+
export const CA_LAYER_NAME = 'control_adapter_layer';
18+
export const CA_LAYER_IMAGE_NAME = 'control_adapter_layer.image';
19+
export const RG_LAYER_NAME = 'regional_guidance_layer';
20+
export const RG_LAYER_LINE_NAME = 'regional_guidance_layer.line';
21+
export const RG_LAYER_OBJECT_GROUP_NAME = 'regional_guidance_layer.object_group';
22+
export const RG_LAYER_RECT_NAME = 'regional_guidance_layer.rect';
23+
export const INITIAL_IMAGE_LAYER_ID = 'singleton_initial_image_layer';
24+
export const INITIAL_IMAGE_LAYER_NAME = 'initial_image_layer';
25+
export const INITIAL_IMAGE_LAYER_IMAGE_NAME = 'initial_image_layer.image';
26+
export const LAYER_BBOX_NAME = 'layer.bbox';
27+
export const COMPOSITING_RECT_NAME = 'compositing-rect';
28+
29+
// Getters for non-singleton layer and object IDs
30+
export const getRGLayerId = (layerId: string) => `${RG_LAYER_NAME}_${layerId}`;
31+
export const getRGLayerLineId = (layerId: string, lineId: string) => `${layerId}.line_${lineId}`;
32+
export const getRGLayerRectId = (layerId: string, lineId: string) => `${layerId}.rect_${lineId}`;
33+
export const getRGLayerObjectGroupId = (layerId: string, groupId: string) => `${layerId}.objectGroup_${groupId}`;
34+
export const getLayerBboxId = (layerId: string) => `${layerId}.bbox`;
35+
export const getCALayerId = (layerId: string) => `control_adapter_layer_${layerId}`;
36+
export const getCALayerImageId = (layerId: string, imageName: string) => `${layerId}.image_${imageName}`;
37+
export const getIILayerImageId = (layerId: string, imageName: string) => `${layerId}.image_${imageName}`;
38+
export const getIPALayerId = (layerId: string) => `ip_adapter_layer_${layerId}`;

0 commit comments

Comments
 (0)