From 7824cb7a1a434e7eb584ea30f594529cc281d33b Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 26 Jun 2024 18:27:18 +1000 Subject: [PATCH] feat(ui): staging area barely works --- .../listeners/enqueueRequestedLinear.ts | 18 +++++---- .../controlLayers/konva/nodeManager.ts | 40 ++++++++++++------- .../controlLayers/konva/renderers/objects.ts | 2 +- .../controlLayers/konva/renderers/preview.ts | 32 +++++++-------- .../controlLayers/konva/renderers/renderer.ts | 7 +++- 5 files changed, 60 insertions(+), 39 deletions(-) diff --git a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear.ts b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear.ts index bbac10e2a1..277e6b2206 100644 --- a/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear.ts +++ b/invokeai/frontend/web/src/app/store/middleware/listenerMiddleware/listeners/enqueueRequestedLinear.ts @@ -1,7 +1,7 @@ import { enqueueRequested } from 'app/store/actions'; import type { AppStartListening } from 'app/store/middleware/listenerMiddleware'; import { getNodeManager } from 'features/controlLayers/konva/nodeManager'; -import { stagingAreaInitialized } from 'features/controlLayers/store/canvasV2Slice'; +import { stagingAreaBatchIdAdded, stagingAreaInitialized } from 'features/controlLayers/store/canvasV2Slice'; import { isImageViewerOpenChanged } from 'features/gallery/store/gallerySlice'; import { prepareLinearUIBatch } from 'features/nodes/util/graph/buildLinearBatchConfig'; import { buildSD1Graph } from 'features/nodes/util/graph/generation/buildSD1Graph'; @@ -48,12 +48,16 @@ export const addEnqueueRequestedLinear = (startAppListening: AppStartListening) // TODO(psyche): update the backend schema, this is always provided const batchId = enqueueResult.batch.batch_id; assert(batchId, 'No batch ID found in enqueue result'); - dispatch( - stagingAreaInitialized({ - batchIds: [batchId], - bbox: getState().canvasV2.bbox, - }) - ); + if (!state.canvasV2.stagingArea) { + dispatch( + stagingAreaInitialized({ + batchIds: [batchId], + bbox: state.canvasV2.bbox, + }) + ); + } else { + dispatch(stagingAreaBatchIdAdded({ batchId })); + } } finally { req.reset(); } diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/nodeManager.ts b/invokeai/frontend/web/src/features/controlLayers/konva/nodeManager.ts index 4f0b043bb4..73d2202406 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/nodeManager.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/nodeManager.ts @@ -1,6 +1,12 @@ import { getImageDataTransparency } from 'common/util/arrayBuffer'; import { CanvasBackground } from 'features/controlLayers/konva/renderers/background'; -import { CanvasPreview } from 'features/controlLayers/konva/renderers/preview'; +import { + CanvasBbox, + CanvasDocumentSizeOverlay, + CanvasPreview, + CanvasStagingArea, + CanvasTool, +} from 'features/controlLayers/konva/renderers/preview'; import { konvaNodeToBlob, konvaNodeToImageData, previewBlob } from 'features/controlLayers/konva/util'; import type { BrushLineAddedArg, @@ -106,7 +112,7 @@ export class KonvaNodeManager { controlAdapters: Map; layers: Map; regions: Map; - inpaintMask: CanvasInpaintMask | null; + inpaintMask: CanvasInpaintMask; util: Util; stateApi: StateApi; preview: CanvasPreview; @@ -134,23 +140,29 @@ export class KonvaNodeManager { }; this.preview = new CanvasPreview( - this.stage, - this.stateApi.getBbox, - this.stateApi.onBboxTransformed, - this.stateApi.getShiftKey, - this.stateApi.getCtrlKey, - this.stateApi.getMetaKey, - this.stateApi.getAltKey + new CanvasBbox( + this.stateApi.getBbox, + this.stateApi.onBboxTransformed, + this.stateApi.getShiftKey, + this.stateApi.getCtrlKey, + this.stateApi.getMetaKey, + this.stateApi.getAltKey + ), + new CanvasTool(), + new CanvasDocumentSizeOverlay(), + new CanvasStagingArea() ); this.stage.add(this.preview.konvaLayer); this.background = new CanvasBackground(); this.stage.add(this.background.konvaLayer); + this.inpaintMask = new CanvasInpaintMask(this.stateApi.getInpaintMaskState(), this.stateApi.onPosChanged); + this.stage.add(this.inpaintMask.konvaLayer); + this.layers = new Map(); this.regions = new Map(); this.controlAdapters = new Map(); - this.inpaintMask = null; } renderLayers() { @@ -202,10 +214,6 @@ export class KonvaNodeManager { renderInpaintMask() { const inpaintMaskState = this.stateApi.getInpaintMaskState(); - if (!this.inpaintMask) { - this.inpaintMask = new CanvasInpaintMask(inpaintMaskState, this.stateApi.onPosChanged); - this.stage.add(this.inpaintMask.konvaLayer); - } const toolState = this.stateApi.getToolState(); const selectedEntity = this.stateApi.getSelectedEntity(); const maskOpacity = this.stateApi.getMaskOpacity(); @@ -280,6 +288,10 @@ export class KonvaNodeManager { this.background.renderBackground(this.stage); } + renderStagingArea() { + this.preview.stagingArea.render(this.stateApi.getStagingAreaState()); + } + fitDocument() { this.preview.documentSizeOverlay.fitToStage(this.stage, this.stateApi.getDocument(), this.stateApi.setStageAttrs); } 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 bd540b1f20..8a6d81a35f 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/renderers/objects.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/renderers/objects.ts @@ -189,8 +189,8 @@ export class KonvaImage { image: imageEl, }); this.konvaImageGroup.add(this.konvaImage); - this.imageName = imageName; } + this.imageName = imageName; this.isLoading = false; this.isError = false; this.konvaPlaceholderGroup.visible(false); diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/renderers/preview.ts b/invokeai/frontend/web/src/features/controlLayers/konva/renderers/preview.ts index 798052c7a1..c2ada34fc4 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/renderers/preview.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/renderers/preview.ts @@ -101,11 +101,11 @@ export class CanvasStagingArea { return; } - if (stagingArea.selectedImageIndex) { + if (stagingArea.selectedImageIndex !== null) { const imageDTO = stagingArea.images[stagingArea.selectedImageIndex]; assert(imageDTO, 'Image must exist'); if (this.image) { - if (this.image.imageName !== imageDTO.image_name) { + if (!this.image.isLoading && !this.image.isError && this.image.imageName !== imageDTO.image_name) { await this.image.updateImageSource(imageDTO.image_name); } } else { @@ -114,8 +114,8 @@ export class CanvasStagingArea { imageObject: { id: 'staging-area-image', type: 'image', - x: 0, - y: 0, + x: stagingArea.bbox.x, + y: stagingArea.bbox.y, width, height, filters: [], @@ -126,6 +126,8 @@ export class CanvasStagingArea { }, }, }); + this.group.add(this.image.konvaImageGroup); + await this.image.updateImageSource(imageDTO.image_name); } } } @@ -375,7 +377,6 @@ export class CanvasBbox { NO_ANCHORS: string[] = []; constructor( - stage: Konva.Stage, getBbox: () => IRect, onBboxTransformed: (bbox: IRect) => void, getShiftKey: () => boolean, @@ -446,6 +447,8 @@ export class CanvasBbox { anchorDragBoundFunc: (_oldAbsPos, newAbsPos) => { // This function works with absolute position - that is, a position in "physical" pixels on the screen, as opposed // to konva's internal coordinate system. + const stage = this.transformer.getStage(); + assert(stage, 'Stage must exist'); // We need to snap the anchors to the grid. If the user is holding ctrl/meta, we use the finer 8px grid. const gridSize = getCtrlKey() || getMetaKey() ? 8 : 64; @@ -588,26 +591,23 @@ export class CanvasPreview { stagingArea: CanvasStagingArea; constructor( - stage: Konva.Stage, - getBbox: () => IRect, - onBboxTransformed: (bbox: IRect) => void, - getShiftKey: () => boolean, - getCtrlKey: () => boolean, - getMetaKey: () => boolean, - getAltKey: () => boolean + bbox: CanvasBbox, + tool: CanvasTool, + documentSizeOverlay: CanvasDocumentSizeOverlay, + stagingArea: CanvasStagingArea ) { this.konvaLayer = new Konva.Layer({ listening: true }); - this.bbox = new CanvasBbox(stage, getBbox, onBboxTransformed, getShiftKey, getCtrlKey, getMetaKey, getAltKey); + this.bbox = bbox; this.konvaLayer.add(this.bbox.group); - this.tool = new CanvasTool(); + this.tool = tool; this.konvaLayer.add(this.tool.group); - this.documentSizeOverlay = new CanvasDocumentSizeOverlay(); + this.documentSizeOverlay = documentSizeOverlay; this.konvaLayer.add(this.documentSizeOverlay.group); - this.stagingArea = new CanvasStagingArea(); + this.stagingArea = stagingArea; this.konvaLayer.add(this.stagingArea.group); } } diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/renderers/renderer.ts b/invokeai/frontend/web/src/features/controlLayers/konva/renderers/renderer.ts index 01d3e93d99..1551752ef2 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/renderers/renderer.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/renderers/renderer.ts @@ -343,7 +343,7 @@ export const initializeRenderer = ( // the entire state over when needed. const debouncedUpdateBboxes = debounce(updateBboxes, 300); - const renderCanvas = () => { + const renderCanvas = async () => { canvasV2 = store.getState().canvasV2; if (prevCanvasV2 === canvasV2 && !isFirstRender) { @@ -408,6 +408,11 @@ export const initializeRenderer = ( // debouncedUpdateBboxes(stage, canvasV2.layers, canvasV2.controlAdapters, canvasV2.regions, onBboxChanged); } + if (isFirstRender || canvasV2.stagingArea !== prevCanvasV2.stagingArea) { + logIfDebugging('Rendering staging area'); + manager.renderStagingArea(); + } + if ( isFirstRender || canvasV2.layers.entities !== prevCanvasV2.layers.entities ||