diff --git a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts index bdcd470014..118d0e0a22 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/store/regionalPromptsSlice.ts @@ -229,8 +229,8 @@ export const regionalPromptsSlice = createSlice({ strokeWidth: state.brushSize, }); layer.bboxNeedsUpdate = true; - if (!layer.hasEraserStrokes) { - layer.hasEraserStrokes = tool === 'eraser'; + if (!layer.hasEraserStrokes && tool === 'eraser') { + layer.hasEraserStrokes = true; } } }, diff --git a/invokeai/frontend/web/src/features/regionalPrompts/util/bbox.ts b/invokeai/frontend/web/src/features/regionalPrompts/util/bbox.ts index 0f6e1a2f36..1955ad331f 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/util/bbox.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/util/bbox.ts @@ -1,8 +1,8 @@ import openBase64ImageInTab from 'common/util/openBase64ImageInTab'; import { imageDataToDataURL } from 'features/canvas/util/blobToDataURL'; +import { REGIONAL_PROMPT_LAYER_OBJECT_GROUP_NAME } from 'features/regionalPrompts/store/regionalPromptsSlice'; import Konva from 'konva'; import type { Layer as KonvaLayerType } from 'konva/lib/Layer'; -import type { Node as KonvaNodeType, NodeConfig as KonvaNodeConfigType } from 'konva/lib/Node'; import type { IRect } from 'konva/lib/types'; import { assert } from 'tsafe'; @@ -57,11 +57,7 @@ const getImageDataBbox = (imageData: ImageData): Extents | null => { * @param filterChildren Optional filter function to exclude certain children from the bounding box calculation. Defaults to including all children. * @param preview Whether to open a new tab displaying the rendered layer, which is used to calculate the bbox. */ -export const getKonvaLayerBbox = ( - layer: KonvaLayerType, - filterChildren?: (item: KonvaNodeType) => boolean, - preview: boolean = false -): IRect | null => { +export const getKonvaLayerBbox = (layer: KonvaLayerType, preview: boolean = false): IRect | null => { // To calculate the layer's bounding box, we must first export it to a pixel array, then do some math. // // Though it is relatively fast, we can't use Konva's `getClientRect`. It programmatically determines the rect @@ -69,7 +65,6 @@ export const getKonvaLayerBbox = ( // // This doesn't work when some shapes are drawn with composite operations that "erase" pixels, like eraser lines. // These shapes' extents are still calculated as if they were solid, leading to a bounding box that is too large. - const stage = layer.getStage(); // Construct and offscreen canvas on which we will do the bbox calculations. @@ -84,8 +79,12 @@ export const getKonvaLayerBbox = ( const layerClone = layer.clone(); offscreenStage.add(layerClone); - if (filterChildren) { - for (const child of layerClone.getChildren(filterChildren)) { + for (const child of layerClone.getChildren()) { + if (child.name() === REGIONAL_PROMPT_LAYER_OBJECT_GROUP_NAME) { + // We need to cache the group to ensure it composites out eraser strokes correctly + child.cache(); + } else { + // Filter out unwanted children. child.destroy(); } } diff --git a/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts b/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts index 7a09d4ac39..54c6730724 100644 --- a/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts +++ b/invokeai/frontend/web/src/features/regionalPrompts/util/renderers.ts @@ -16,7 +16,6 @@ import { } from 'features/regionalPrompts/store/regionalPromptsSlice'; import { getKonvaLayerBbox } from 'features/regionalPrompts/util/bbox'; import Konva from 'konva'; -import type { Node, NodeConfig } from 'konva/lib/Node'; import type { IRect, Vector2d } from 'konva/lib/types'; import type { RgbColor } from 'react-colorful'; import { assert } from 'tsafe'; @@ -30,9 +29,6 @@ const BRUSH_PREVIEW_BORDER_OUTER_COLOR = 'rgba(255,255,255,0.8)'; const GET_CLIENT_RECT_CONFIG = { skipTransform: true }; const mapId = (object: { id: string }) => object.id; -const selectPromptLayerObjectGroup = (item: Node) => { - return item.name() !== REGIONAL_PROMPT_LAYER_OBJECT_GROUP_NAME; -}; const getIsSelected = (layerId?: string | null) => { if (!layerId) { return false; @@ -345,7 +341,7 @@ export const renderBbox = ( if (reduxLayer.bboxNeedsUpdate && reduxLayer.objects.length) { // We only need to use the pixel-perfect bounding box if the layer has eraser strokes bbox = reduxLayer.hasEraserStrokes - ? getKonvaLayerBbox(konvaLayer, selectPromptLayerObjectGroup) + ? getKonvaLayerBbox(konvaLayer) : konvaLayer.getClientRect(GET_CLIENT_RECT_CONFIG); // Update the layer's bbox in the redux store @@ -382,6 +378,7 @@ export const renderBbox = ( }); konvaLayer.add(rect); } + rect.setAttrs({ visible: true, listening: true,