From 19859446593797d698b31ba9eaaf67afaaf657ec Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Thu, 6 Jun 2024 18:44:16 +1000 Subject: [PATCH] feat(ui): layers manage their own bbox --- .../components/StageComponent.tsx | 9 --- .../controlLayers/konva/renderers/bbox.ts | 60 +------------------ .../controlLayers/konva/renderers/layers.ts | 4 +- .../controlLayers/konva/renderers/objects.ts | 21 ++++++- .../konva/renderers/rasterLayer.ts | 20 +++++++ .../controlLayers/konva/renderers/rgLayer.ts | 20 +++++++ 6 files changed, 62 insertions(+), 72 deletions(-) diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx index dcead425f6..a98cff2b6b 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx @@ -281,15 +281,6 @@ const useStageRenderer = ( state.size.height, ]); - useLayoutEffect(() => { - log.trace('Rendering bbox'); - if (asPreview) { - // Preview should not display bboxes - return; - } - renderers.renderBboxes(stage, state.layers, tool); - }, [stage, asPreview, state.layers, tool, onBboxChanged, renderers]); - useLayoutEffect(() => { if (asPreview) { // Preview should not check for transparency diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/renderers/bbox.ts b/invokeai/frontend/web/src/features/controlLayers/konva/renderers/bbox.ts index cd052b5eb3..316ef85110 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/renderers/bbox.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/renderers/bbox.ts @@ -1,13 +1,12 @@ import openBase64ImageInTab from 'common/util/openBase64ImageInTab'; import { imageDataToDataURL } from 'features/canvas/util/blobToDataURL'; -import { BBOX_SELECTED_STROKE } from 'features/controlLayers/konva/constants'; import { - getLayerBboxId, LAYER_BBOX_NAME, RASTER_LAYER_OBJECT_GROUP_NAME, RG_LAYER_OBJECT_GROUP_NAME, } from 'features/controlLayers/konva/naming'; -import type { Layer, Tool } from 'features/controlLayers/store/types'; +import { createBboxRect } from 'features/controlLayers/konva/renderers/objects'; +import type { Layer } from 'features/controlLayers/store/types'; import { isRegionalGuidanceLayer, isRGOrRasterlayer } from 'features/controlLayers/store/types'; import Konva from 'konva'; import type { IRect } from 'konva/lib/types'; @@ -175,22 +174,6 @@ export const getLayerBboxFast = (layer: Konva.Layer): IRect => { }; }; -/** - * Creates a bounding box rect for a layer. - * @param layerState The layer state for the layer to create the bounding box for - * @param konvaLayer The konva layer to attach the bounding box to - */ -const createBboxRect = (layerState: Layer, konvaLayer: Konva.Layer): Konva.Rect => { - const rect = new Konva.Rect({ - id: getLayerBboxId(layerState.id), - name: LAYER_BBOX_NAME, - strokeWidth: 1, - visible: false, - }); - konvaLayer.add(rect); - return rect; -}; - const filterRGChildren = (node: Konva.Node): boolean => node.name() === RG_LAYER_OBJECT_GROUP_NAME; const filterRasterChildren = (node: Konva.Node): boolean => node.name() === RASTER_LAYER_OBJECT_GROUP_NAME; @@ -230,42 +213,3 @@ export const updateBboxes = ( } } }; - -/** - * Renders the bounding boxes for the layers. - * @param stage The konva stage - * @param layerStates An array of layers to draw bboxes for - * @param tool The current tool - * @returns - */ -export const renderBboxes = (stage: Konva.Stage, layerStates: Layer[], tool: Tool): void => { - // Hide all bboxes so they don't interfere with getClientRect - for (const bboxRect of stage.find(`.${LAYER_BBOX_NAME}`)) { - bboxRect.visible(false); - bboxRect.listening(false); - } - // No selected layer or not using the move tool - nothing more to do here - if (tool !== 'move') { - return; - } - - for (const layer of layerStates.filter(isRGOrRasterlayer)) { - if (!layer.bbox) { - continue; - } - const konvaLayer = stage.findOne(`#${layer.id}`); - assert(konvaLayer, `Layer ${layer.id} not found in stage`); - - const bboxRect = konvaLayer.findOne(`.${LAYER_BBOX_NAME}`) ?? createBboxRect(layer, konvaLayer); - - bboxRect.setAttrs({ - visible: !layer.bboxNeedsUpdate, - listening: layer.isSelected, - x: layer.bbox.x, - y: layer.bbox.y, - width: layer.bbox.width, - height: layer.bbox.height, - stroke: layer.isSelected ? BBOX_SELECTED_STROKE : '', - }); - } -}; diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/renderers/layers.ts b/invokeai/frontend/web/src/features/controlLayers/konva/renderers/layers.ts index 8243b81504..2b5fea493f 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/renderers/layers.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/renderers/layers.ts @@ -1,7 +1,7 @@ import { DEBOUNCE_MS } from 'features/controlLayers/konva/constants'; import { BACKGROUND_LAYER_ID, TOOL_PREVIEW_LAYER_ID } from 'features/controlLayers/konva/naming'; import { renderBackground } from 'features/controlLayers/konva/renderers/background'; -import { renderBboxes, updateBboxes } from 'features/controlLayers/konva/renderers/bbox'; +import { updateBboxes } from 'features/controlLayers/konva/renderers/bbox'; import { renderCALayer } from 'features/controlLayers/konva/renderers/caLayer'; import { renderIILayer } from 'features/controlLayers/konva/renderers/iiLayer'; import { renderNoLayersMessage } from 'features/controlLayers/konva/renderers/noLayersMessage'; @@ -90,7 +90,6 @@ const renderLayers = ( export const renderers = { renderToolPreview, renderLayers, - renderBboxes, renderBackground, renderNoLayersMessage, arrangeLayers, @@ -105,7 +104,6 @@ export const renderers = { const getDebouncedRenderers = (ms = DEBOUNCE_MS): typeof renderers => ({ renderToolPreview: debounce(renderToolPreview, ms), renderLayers: debounce(renderLayers, ms), - renderBboxes: debounce(renderBboxes, ms), renderBackground: debounce(renderBackground, ms), renderNoLayersMessage: debounce(renderNoLayersMessage, ms), arrangeLayers: debounce(arrangeLayers, ms), diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/renderers/objects.ts b/invokeai/frontend/web/src/features/controlLayers/konva/renderers/objects.ts index b6ca47650c..2d0747effd 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/renderers/objects.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/renderers/objects.ts @@ -1,6 +1,6 @@ import { rgbaColorToString } from 'features/canvas/util/colorToString'; -import { getObjectGroupId } from 'features/controlLayers/konva/naming'; -import type { BrushLine, EraserLine, ImageObject, RectShape } from 'features/controlLayers/store/types'; +import { getLayerBboxId, getObjectGroupId, LAYER_BBOX_NAME } from 'features/controlLayers/konva/naming'; +import type { BrushLine, EraserLine, ImageObject, Layer, RectShape } from 'features/controlLayers/store/types'; import { DEFAULT_RGBA_COLOR } from 'features/controlLayers/store/types'; import { t } from 'i18next'; import Konva from 'konva'; @@ -193,6 +193,23 @@ export const createImageObjectGroup = async ( }); return konvaImageGroup; }; + +/** + * Creates a bounding box rect for a layer. + * @param layerState The layer state for the layer to create the bounding box for + * @param konvaLayer The konva layer to attach the bounding box to + */ +export const createBboxRect = (layerState: Layer, konvaLayer: Konva.Layer): Konva.Rect => { + const rect = new Konva.Rect({ + id: getLayerBboxId(layerState.id), + name: LAYER_BBOX_NAME, + strokeWidth: 1, + visible: false, + }); + konvaLayer.add(rect); + return rect; +}; + /** * Creates a konva group for a layer's objects. * @param konvaLayer The konva layer to add the object group to diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/renderers/rasterLayer.ts b/invokeai/frontend/web/src/features/controlLayers/konva/renderers/rasterLayer.ts index 84fad00cb9..af55f7f1f2 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/renderers/rasterLayer.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/renderers/rasterLayer.ts @@ -1,4 +1,6 @@ +import { BBOX_SELECTED_STROKE } from 'features/controlLayers/konva/constants'; import { + LAYER_BBOX_NAME, RASTER_LAYER_BRUSH_LINE_NAME, RASTER_LAYER_ERASER_LINE_NAME, RASTER_LAYER_IMAGE_NAME, @@ -7,6 +9,7 @@ import { RASTER_LAYER_RECT_SHAPE_NAME, } from 'features/controlLayers/konva/naming'; import { + createBboxRect, createBrushLine, createEraserLine, createImageObjectGroup, @@ -141,5 +144,22 @@ export const renderRasterLayer = async ( konvaLayer.visible(layerState.isEnabled); } + const bboxRect = konvaLayer.findOne(`.${LAYER_BBOX_NAME}`) ?? createBboxRect(layerState, konvaLayer); + + if (layerState.bbox) { + const active = !layerState.bboxNeedsUpdate && layerState.isSelected && tool === 'move'; + bboxRect.setAttrs({ + visible: active, + listening: active, + x: layerState.bbox.x, + y: layerState.bbox.y, + width: layerState.bbox.width, + height: layerState.bbox.height, + stroke: layerState.isSelected ? BBOX_SELECTED_STROKE : '', + }); + } else { + bboxRect.visible(false); + } + konvaObjectGroup.opacity(layerState.opacity); }; diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/renderers/rgLayer.ts b/invokeai/frontend/web/src/features/controlLayers/konva/renderers/rgLayer.ts index 4321a85c01..32a3f0e3bb 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/renderers/rgLayer.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/renderers/rgLayer.ts @@ -1,6 +1,8 @@ import { rgbColorToString } from 'features/canvas/util/colorToString'; +import { BBOX_SELECTED_STROKE } from 'features/controlLayers/konva/constants'; import { COMPOSITING_RECT_NAME, + LAYER_BBOX_NAME, RG_LAYER_BRUSH_LINE_NAME, RG_LAYER_ERASER_LINE_NAME, RG_LAYER_NAME, @@ -9,6 +11,7 @@ import { } from 'features/controlLayers/konva/naming'; import { getLayerBboxFast } from 'features/controlLayers/konva/renderers/bbox'; import { + createBboxRect, createBrushLine, createEraserLine, createObjectGroup, @@ -227,4 +230,21 @@ export const renderRGLayer = ( // Updating group opacity does not require re-caching konvaObjectGroup.opacity(globalMaskLayerOpacity); } + + const bboxRect = konvaLayer.findOne(`.${LAYER_BBOX_NAME}`) ?? createBboxRect(layerState, konvaLayer); + + if (layerState.bbox) { + const active = !layerState.bboxNeedsUpdate && layerState.isSelected && tool === 'move'; + bboxRect.setAttrs({ + visible: active, + listening: active, + x: layerState.bbox.x, + y: layerState.bbox.y, + width: layerState.bbox.width, + height: layerState.bbox.height, + stroke: layerState.isSelected ? BBOX_SELECTED_STROKE : '', + }); + } else { + bboxRect.visible(false); + } };