diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/DeleteAllLayersButton.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/DeleteAllLayersButton.tsx
new file mode 100644
index 0000000000..59d30f8a07
--- /dev/null
+++ b/invokeai/frontend/web/src/features/regionalPrompts/components/DeleteAllLayersButton.tsx
@@ -0,0 +1,15 @@
+import { Button } from '@invoke-ai/ui-library';
+import { useAppDispatch } from 'app/store/storeHooks';
+import { allLayersDeleted } from 'features/regionalPrompts/store/regionalPromptsSlice';
+import { memo, useCallback } from 'react';
+
+export const DeleteAllLayersButton = memo(() => {
+ const dispatch = useAppDispatch();
+ const onClick = useCallback(() => {
+ dispatch(allLayersDeleted());
+ }, [dispatch]);
+
+ return ;
+});
+
+DeleteAllLayersButton.displayName = 'DeleteAllLayersButton';
diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsEditor.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsEditor.tsx
index 66fcd6aba5..59eabb0622 100644
--- a/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsEditor.tsx
+++ b/invokeai/frontend/web/src/features/regionalPrompts/components/RegionalPromptsEditor.tsx
@@ -4,10 +4,10 @@ import { createMemoizedSelector } from 'app/store/createMemoizedSelector';
import { useAppSelector } from 'app/store/storeHooks';
import { AddLayerButton } from 'features/regionalPrompts/components/AddLayerButton';
import { BrushSize } from 'features/regionalPrompts/components/BrushSize';
+import { DeleteAllLayersButton } from 'features/regionalPrompts/components/DeleteAllLayersButton';
import { StageComponent } from 'features/regionalPrompts/components/imperative/konvaApiDraft';
import { LayerListItem } from 'features/regionalPrompts/components/LayerListItem';
import { PromptLayerOpacity } from 'features/regionalPrompts/components/PromptLayerOpacity';
-import { RegionalPromptsStage } from 'features/regionalPrompts/components/RegionalPromptsStage';
import { ToolChooser } from 'features/regionalPrompts/components/ToolChooser';
import { selectRegionalPromptsSlice } from 'features/regionalPrompts/store/regionalPromptsSlice';
import { getRegionalPromptLayerBlobs } from 'features/regionalPrompts/util/getLayerBlobs';
@@ -27,8 +27,11 @@ export const RegionalPromptsEditor = memo(() => {
return (
-
-
+
+
+
+
+
diff --git a/invokeai/frontend/web/src/features/regionalPrompts/components/imperative/konvaApiDraft.tsx b/invokeai/frontend/web/src/features/regionalPrompts/components/imperative/konvaApiDraft.tsx
index 761bef5805..fcfba724df 100644
--- a/invokeai/frontend/web/src/features/regionalPrompts/components/imperative/konvaApiDraft.tsx
+++ b/invokeai/frontend/web/src/features/regionalPrompts/components/imperative/konvaApiDraft.tsx
@@ -113,7 +113,7 @@ const renderBrushPreview = (
});
};
-const renderLayers = (
+export const renderLayers = (
stage: Konva.Stage,
reduxLayers: Layer[],
selectedLayerId: string | null,
@@ -369,7 +369,11 @@ export const StageComponent = () => {
const container = useStore($container);
return (
<>
-
+
>
);
diff --git a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts
index 4cd5658095..106f982378 100644
--- a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts
+++ b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts
@@ -159,6 +159,10 @@ export const regionalPromptsSlice = createSlice({
}
layer.bbox = bbox;
},
+ allLayersDeleted: (state) => {
+ state.layers = [];
+ state.selectedLayer = null;
+ },
promptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string }>) => {
const { layerId, prompt } = action.payload;
const layer = state.layers.find((l) => l.id === layerId);
@@ -258,6 +262,7 @@ export const {
layerTranslated,
layerBboxChanged,
promptLayerOpacityChanged,
+ allLayersDeleted,
} = regionalPromptsSlice.actions;
export const selectRegionalPromptsSlice = (state: RootState) => state.regionalPrompts;
diff --git a/invokeai/frontend/web/src/features/regionalPrompts/util/getLayerBlobs.ts b/invokeai/frontend/web/src/features/regionalPrompts/util/getLayerBlobs.ts
index 5129c3c9d0..b67dd92302 100644
--- a/invokeai/frontend/web/src/features/regionalPrompts/util/getLayerBlobs.ts
+++ b/invokeai/frontend/web/src/features/regionalPrompts/util/getLayerBlobs.ts
@@ -1,7 +1,8 @@
+import { getStore } from 'app/store/nanostores/store';
import openBase64ImageInTab from 'common/util/openBase64ImageInTab';
import { blobToDataURL } from 'features/canvas/util/blobToDataURL';
-import { selectPromptLayerObjectGroup } from 'features/regionalPrompts/components/LayerComponent';
-import { getStage, REGIONAL_PROMPT_LAYER_NAME } from 'features/regionalPrompts/store/regionalPromptsSlice';
+import { renderLayers } from 'features/regionalPrompts/components/imperative/konvaApiDraft';
+import { REGIONAL_PROMPT_LAYER_NAME } from 'features/regionalPrompts/store/regionalPromptsSlice';
import Konva from 'konva';
import { assert } from 'tsafe';
@@ -15,40 +16,27 @@ export const getRegionalPromptLayerBlobs = async (
layerIds?: string[],
preview: boolean = false
): Promise> => {
- const stage = getStage();
-
- // This automatically omits layers that are not rendered. Rendering is controlled by the layer's `isVisible` flag in redux.
- const regionalPromptLayers = stage.getLayers().filter((l) => {
- console.log(l.name(), l.id());
- const isRegionalPromptLayer = l.name() === REGIONAL_PROMPT_LAYER_NAME;
- const isRequestedLayerId = layerIds ? layerIds.includes(l.id()) : true;
- return isRegionalPromptLayer && isRequestedLayerId;
- });
-
- // We need to reconstruct each layer to only output the desired data. This logic mirrors the logic in
- // `getKonvaLayerBbox()` in `invokeai/frontend/web/src/features/regionalPrompts/util/bbox.ts`
- const offscreenStageContainer = document.createElement('div');
- const offscreenStage = new Konva.Stage({
- container: offscreenStageContainer,
- width: stage.width(),
- height: stage.height(),
- });
+ const state = getStore().getState();
+ const container = document.createElement('div');
+ const stage = new Konva.Stage({ container, width: state.generation.width, height: state.generation.height });
+ renderLayers(stage, state.regionalPrompts.layers, state.regionalPrompts.selectedLayer);
+ const layers = stage.find(`.${REGIONAL_PROMPT_LAYER_NAME}`);
const blobs: Record = {};
- for (const layer of regionalPromptLayers) {
- const layerClone = layer.clone();
- for (const child of layerClone.getChildren()) {
- if (selectPromptLayerObjectGroup(child)) {
- child.destroy();
- } else {
- // We need to re-cache to handle children with transparency and multiple objects - like prompt region layers.
- child.cache();
- }
+ // First remove all layers
+ for (const layer of layers) {
+ layer.remove();
+ }
+
+ // Next render each layer to a blob
+ for (const layer of layers) {
+ if (layerIds && !layerIds.includes(layer.id())) {
+ continue;
}
- offscreenStage.add(layerClone);
+ stage.add(layer);
const blob = await new Promise((resolve) => {
- offscreenStage.toBlob({
+ stage.toBlob({
callback: (blob) => {
assert(blob, 'Blob is null');
resolve(blob);
@@ -60,7 +48,7 @@ export const getRegionalPromptLayerBlobs = async (
const base64 = await blobToDataURL(blob);
openBase64ImageInTab([{ base64, caption: layer.id() }]);
}
- layerClone.destroy();
+ layer.remove();
blobs[layer.id()] = blob;
}