From f7bbc4004acb59123a5dda03175d0dda130da206 Mon Sep 17 00:00:00 2001 From: psychedelicious <4822129+psychedelicious@users.noreply.github.com> Date: Wed, 3 May 2023 14:35:47 +1000 Subject: [PATCH] feat(ui): wip canvas nodes migration 3 --- .../canvas/hooks/useGetCanvasNodeType.ts | 30 ------ .../src/features/canvas/store/canvasSlice.ts | 8 +- .../src/features/canvas/store/canvasTypes.ts | 1 + ...{getCanvasDataURLs.ts => getCanvasData.ts} | 3 +- .../features/canvas/util/getCanvasNodeType.ts | 102 ------------------ .../nodes/components/NodeGraphOverlay.tsx | 2 +- ...uildLinearGraph.ts => buildCanvasGraph.ts} | 71 ++++-------- .../features/nodes/util/buildLinearGraph.ts | 39 +++++++ .../buildNodesGraph.ts | 0 .../UnifiedCanvas/UnifiedCanvasWorkarea.tsx | 27 ----- .../web/src/services/thunks/session.ts | 8 +- 11 files changed, 70 insertions(+), 221 deletions(-) delete mode 100644 invokeai/frontend/web/src/features/canvas/hooks/useGetCanvasNodeType.ts rename invokeai/frontend/web/src/features/canvas/util/{getCanvasDataURLs.ts => getCanvasData.ts} (97%) delete mode 100644 invokeai/frontend/web/src/features/canvas/util/getCanvasNodeType.ts rename invokeai/frontend/web/src/features/nodes/util/{linearGraphBuilder/buildLinearGraph.ts => buildCanvasGraph.ts} (51%) create mode 100644 invokeai/frontend/web/src/features/nodes/util/buildLinearGraph.ts rename invokeai/frontend/web/src/features/nodes/util/{nodesGraphBuilder => }/buildNodesGraph.ts (100%) diff --git a/invokeai/frontend/web/src/features/canvas/hooks/useGetCanvasNodeType.ts b/invokeai/frontend/web/src/features/canvas/hooks/useGetCanvasNodeType.ts deleted file mode 100644 index 91f8e50d29..0000000000 --- a/invokeai/frontend/web/src/features/canvas/hooks/useGetCanvasNodeType.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { createSelector } from '@reduxjs/toolkit'; -import { useAppSelector } from 'app/store/storeHooks'; -import { canvasSelector } from '../store/canvasSelectors'; -import { useMemo } from 'react'; -import { getCanvasNodeType } from '../util/getCanvasNodeType'; - -const selector = createSelector(canvasSelector, (canvas) => { - const { - layerState: { objects }, - boundingBoxCoordinates, - boundingBoxDimensions, - stageScale, - isMaskEnabled, - } = canvas; - return { - objects, - boundingBoxCoordinates, - boundingBoxDimensions, - stageScale, - isMaskEnabled, - }; -}); - -export const useGetCanvasNodeType = () => { - const data = useAppSelector(selector); - - const nodeType = useMemo(() => getCanvasNodeType(data), [data]); - - return nodeType; -}; diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts index 5ff8ea4295..22d4270e18 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasSlice.ts @@ -29,6 +29,7 @@ import { isCanvasBaseImage, isCanvasMaskLine, } from './canvasTypes'; +import { invocationComplete } from 'services/events/actions'; export const initialLayerState: CanvasLayerState = { objects: [], @@ -289,7 +290,7 @@ export const canvasSlice = createSlice({ state, action: PayloadAction<{ boundingBox: IRect; - image: InvokeAI._Image; + image: InvokeAI.Image; }> ) => { const { boundingBox, image } = action.payload; @@ -815,6 +816,11 @@ export const canvasSlice = createSlice({ state.isTransformingBoundingBox = false; }, }, + extraReducers(builder) { + builder.addCase(invocationComplete, (state, action) => { + // + }); + }, }); export const { diff --git a/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts b/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts index 2eec0e9bed..df24e9ee4d 100644 --- a/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts +++ b/invokeai/frontend/web/src/features/canvas/store/canvasTypes.ts @@ -162,4 +162,5 @@ export interface CanvasState { stageDimensions: Dimensions; stageScale: number; tool: CanvasTool; + pendingBoundingBox?: IRect; } diff --git a/invokeai/frontend/web/src/features/canvas/util/getCanvasDataURLs.ts b/invokeai/frontend/web/src/features/canvas/util/getCanvasData.ts similarity index 97% rename from invokeai/frontend/web/src/features/canvas/util/getCanvasDataURLs.ts rename to invokeai/frontend/web/src/features/canvas/util/getCanvasData.ts index e309057eee..c6192f0a09 100644 --- a/invokeai/frontend/web/src/features/canvas/util/getCanvasDataURLs.ts +++ b/invokeai/frontend/web/src/features/canvas/util/getCanvasData.ts @@ -16,7 +16,7 @@ import { masks } from 'dateformat'; const moduleLog = log.child({ namespace: 'getCanvasDataURLs' }); -export const getCanvasDataURLs = (state: RootState) => { +export const getCanvasData = (state: RootState) => { const canvasBaseLayer = getCanvasBaseLayer(); const canvasStage = getCanvasStage(); @@ -85,7 +85,6 @@ export const getCanvasDataURLs = (state: RootState) => { offscreenContainer.remove(); if (!maskImageData) { - moduleLog.error('Unable to get mask stage context'); return; } diff --git a/invokeai/frontend/web/src/features/canvas/util/getCanvasNodeType.ts b/invokeai/frontend/web/src/features/canvas/util/getCanvasNodeType.ts deleted file mode 100644 index baa250031b..0000000000 --- a/invokeai/frontend/web/src/features/canvas/util/getCanvasNodeType.ts +++ /dev/null @@ -1,102 +0,0 @@ -import { RootState } from 'app/store/store'; -import { getCanvasBaseLayer, getCanvasStage } from './konvaInstanceProvider'; -import { - CanvasObject, - Dimensions, - isCanvasMaskLine, -} from '../store/canvasTypes'; -import { buildMaskStage, getStageImageData } from './generateMask'; -import { log } from 'app/logging/useLogger'; -import { - areAnyPixelsBlack, - getImageDataTransparency, -} from 'common/util/arrayBuffer'; -import { getNodeType } from 'features/nodes/util/getNodeType'; -import { Vector2d } from 'konva/lib/types'; - -const moduleLog = log.child({ namespace: 'getCanvasNodeTypes' }); - -export type GetCanvasNodeTypeArg = { - objects: CanvasObject[]; - boundingBoxCoordinates: Vector2d; - boundingBoxDimensions: Dimensions; - stageScale: number; - isMaskEnabled: boolean; -}; - -export const getCanvasNodeType = (arg: GetCanvasNodeTypeArg) => { - const canvasBaseLayer = getCanvasBaseLayer(); - const canvasStage = getCanvasStage(); - - if (!canvasBaseLayer || !canvasStage) { - moduleLog.error('Unable to find canvas / stage'); - return; - } - - const { - objects, - boundingBoxCoordinates, - boundingBoxDimensions, - stageScale, - isMaskEnabled, - } = arg; - - const boundingBox = { - ...boundingBoxCoordinates, - ...boundingBoxDimensions, - }; - - const tempScale = canvasBaseLayer.scale(); - - canvasBaseLayer.scale({ - x: 1 / stageScale, - y: 1 / stageScale, - }); - - const absPos = canvasBaseLayer.getAbsolutePosition(); - - const scaledBoundingBox = { - x: boundingBox.x + absPos.x, - y: boundingBox.y + absPos.y, - width: boundingBox.width, - height: boundingBox.height, - }; - - const { stage: maskStage, offscreenContainer } = buildMaskStage( - isMaskEnabled ? objects.filter(isCanvasMaskLine) : [], - scaledBoundingBox - ); - - const maskImageData = getStageImageData(maskStage, scaledBoundingBox); - - offscreenContainer.remove(); - - if (!maskImageData) { - moduleLog.error('Unable to get mask stage context'); - return; - } - - const ctx = canvasBaseLayer.getContext(); - - const baseImageData = ctx.getImageData( - boundingBox.x + absPos.x, - boundingBox.y + absPos.y, - boundingBox.width, - boundingBox.height - ); - - canvasBaseLayer.scale(tempScale); - - const { - isPartiallyTransparent: baseIsPartiallyTransparent, - isFullyTransparent: baseIsFullyTransparent, - } = getImageDataTransparency(baseImageData); - - const doesMaskHaveBlackPixels = areAnyPixelsBlack(maskImageData); - - return getNodeType( - baseIsPartiallyTransparent, - baseIsFullyTransparent, - doesMaskHaveBlackPixels - ); -}; diff --git a/invokeai/frontend/web/src/features/nodes/components/NodeGraphOverlay.tsx b/invokeai/frontend/web/src/features/nodes/components/NodeGraphOverlay.tsx index 90c8e1396f..bcf02eabf5 100644 --- a/invokeai/frontend/web/src/features/nodes/components/NodeGraphOverlay.tsx +++ b/invokeai/frontend/web/src/features/nodes/components/NodeGraphOverlay.tsx @@ -2,7 +2,7 @@ import { Box } from '@chakra-ui/react'; import { RootState } from 'app/store/store'; import { useAppSelector } from 'app/store/storeHooks'; import { memo } from 'react'; -import { buildNodesGraph } from '../util/nodesGraphBuilder/buildNodesGraph'; +import { buildNodesGraph } from '../util/buildNodesGraph'; const NodeGraphOverlay = () => { const state = useAppSelector((state: RootState) => state); diff --git a/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/buildCanvasGraph.ts similarity index 51% rename from invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildLinearGraph.ts rename to invokeai/frontend/web/src/features/nodes/util/buildCanvasGraph.ts index e0dd2f4843..24c057c04a 100644 --- a/invokeai/frontend/web/src/features/nodes/util/linearGraphBuilder/buildLinearGraph.ts +++ b/invokeai/frontend/web/src/features/nodes/util/buildCanvasGraph.ts @@ -1,58 +1,31 @@ import { RootState } from 'app/store/store'; import { DataURLToImageInvocation, Graph } from 'services/api'; -import { buildImg2ImgNode } from './buildImageToImageNode'; -import { buildTxt2ImgNode } from './buildTextToImageNode'; -import { buildRangeNode } from './buildRangeNode'; -import { buildIterateNode } from './buildIterateNode'; -import { buildEdges } from './buildEdges'; +import { buildImg2ImgNode } from './linearGraphBuilder/buildImageToImageNode'; +import { buildTxt2ImgNode } from './linearGraphBuilder/buildTextToImageNode'; +import { buildRangeNode } from './linearGraphBuilder/buildRangeNode'; +import { buildIterateNode } from './linearGraphBuilder/buildIterateNode'; +import { buildEdges } from './linearGraphBuilder/buildEdges'; import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider'; -import { getCanvasDataURLs } from 'features/canvas/util/getCanvasDataURLs'; -import { log } from 'console'; -import { getNodeType } from '../getNodeType'; +import { getCanvasData } from 'features/canvas/util/getCanvasData'; +import { getNodeType } from './getNodeType'; import { v4 as uuidv4 } from 'uuid'; +import { log } from 'app/logging/useLogger'; + +const moduleLog = log.child({ namespace: 'buildCanvasGraph' }); /** - * Builds the Linear workflow graph. + * Builds the Canvas workflow graph. */ -export const buildLinearGraph = (state: RootState): Graph => { - // The base node is either a txt2img or img2img node - const baseNode = state.generation.isImageToImageEnabled - ? buildImg2ImgNode(state) - : buildTxt2ImgNode(state); - - // We always range and iterate nodes, no matter the iteration count - // This is required to provide the correct seeds to the backend engine - const rangeNode = buildRangeNode(state); - const iterateNode = buildIterateNode(); - - // Build the edges for the nodes selected. - const edges = buildEdges(baseNode, rangeNode, iterateNode); - - // Assemble! - const graph = { - nodes: { - [rangeNode.id]: rangeNode, - [iterateNode.id]: iterateNode, - [baseNode.id]: baseNode, - }, - edges, - }; - - // TODO: hires fix requires latent space upscaling; we don't have nodes for this yet - - return graph; -}; - -/** - * Builds the Linear workflow graph. - */ -export const buildCanvasGraph = (state: RootState): Graph => { - const c = getCanvasDataURLs(state); +export const buildCanvasGraph = (state: RootState): Graph | undefined => { + const c = getCanvasData(state); if (!c) { - throw 'problm creating canvas graph'; + moduleLog.error('Unable to create canvas graph'); + return; } + moduleLog.debug({ data: c }, 'Built canvas data'); + const { baseDataURL, maskDataURL, @@ -61,21 +34,13 @@ export const buildCanvasGraph = (state: RootState): Graph => { doesMaskHaveBlackPixels, } = c; - console.log({ - baseDataURL, - maskDataURL, - baseIsPartiallyTransparent, - baseIsFullyTransparent, - doesMaskHaveBlackPixels, - }); - const nodeType = getNodeType( baseIsPartiallyTransparent, baseIsFullyTransparent, doesMaskHaveBlackPixels ); - console.log(nodeType); + moduleLog.debug(`Node type ${nodeType}`); // The base node is either a txt2img or img2img node const baseNode = diff --git a/invokeai/frontend/web/src/features/nodes/util/buildLinearGraph.ts b/invokeai/frontend/web/src/features/nodes/util/buildLinearGraph.ts new file mode 100644 index 0000000000..f247964c72 --- /dev/null +++ b/invokeai/frontend/web/src/features/nodes/util/buildLinearGraph.ts @@ -0,0 +1,39 @@ +import { RootState } from 'app/store/store'; +import { Graph } from 'services/api'; +import { buildImg2ImgNode } from './linearGraphBuilder/buildImageToImageNode'; +import { buildTxt2ImgNode } from './linearGraphBuilder/buildTextToImageNode'; +import { buildRangeNode } from './linearGraphBuilder/buildRangeNode'; +import { buildIterateNode } from './linearGraphBuilder/buildIterateNode'; +import { buildEdges } from './linearGraphBuilder/buildEdges'; + +/** + * Builds the Linear workflow graph. + */ +export const buildLinearGraph = (state: RootState): Graph => { + // The base node is either a txt2img or img2img node + const baseNode = state.generation.isImageToImageEnabled + ? buildImg2ImgNode(state) + : buildTxt2ImgNode(state); + + // We always range and iterate nodes, no matter the iteration count + // This is required to provide the correct seeds to the backend engine + const rangeNode = buildRangeNode(state); + const iterateNode = buildIterateNode(); + + // Build the edges for the nodes selected. + const edges = buildEdges(baseNode, rangeNode, iterateNode); + + // Assemble! + const graph = { + nodes: { + [rangeNode.id]: rangeNode, + [iterateNode.id]: iterateNode, + [baseNode.id]: baseNode, + }, + edges, + }; + + // TODO: hires fix requires latent space upscaling; we don't have nodes for this yet + + return graph; +}; diff --git a/invokeai/frontend/web/src/features/nodes/util/nodesGraphBuilder/buildNodesGraph.ts b/invokeai/frontend/web/src/features/nodes/util/buildNodesGraph.ts similarity index 100% rename from invokeai/frontend/web/src/features/nodes/util/nodesGraphBuilder/buildNodesGraph.ts rename to invokeai/frontend/web/src/features/nodes/util/buildNodesGraph.ts diff --git a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx index 19e8f372b2..ee7f4d242d 100644 --- a/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx +++ b/invokeai/frontend/web/src/features/ui/components/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx @@ -1,29 +1,3 @@ -// import { RootState } from 'app/store/store'; -// import { useAppSelector } from 'app/store/storeHooks'; -// import InvokeWorkarea from 'features/ui/components/InvokeWorkarea'; -// import { activeTabNameSelector } from 'features/ui/store/uiSelectors'; -// import UnifiedCanvasContentBeta from './UnifiedCanvasBeta/UnifiedCanvasContentBeta'; -// import UnifiedCanvasContent from './UnifiedCanvasContent'; -// import UnifiedCanvasParameters from './UnifiedCanvasParameters'; - -// export default function UnifiedCanvasWorkarea() { -// const shouldUseCanvasBetaLayout = useAppSelector( -// (state: RootState) => state.ui.shouldUseCanvasBetaLayout -// ); - -// const activeTabName = useAppSelector(activeTabNameSelector); - -// return ( -// }> -// {activeTabName === 'unifiedCanvas' && -// (shouldUseCanvasBetaLayout ? ( -// -// ) : ( -// -// ))} -// -// ); -// } import { Box, Flex } from '@chakra-ui/react'; import { useAppSelector } from 'app/store/storeHooks'; import { memo } from 'react'; @@ -34,7 +8,6 @@ import ParametersSlide from '../../common/ParametersSlide'; import UnifiedCanvasParameters from './UnifiedCanvasParameters'; import UnifiedCanvasContentBeta from './UnifiedCanvasBeta/UnifiedCanvasContentBeta'; import UnifiedCanvasContent from './UnifiedCanvasContent'; -import { useGetCanvasNodeType } from 'features/canvas/hooks/useGetCanvasNodeType'; const CanvasWorkspace = () => { const shouldPinParametersPanel = useAppSelector( diff --git a/invokeai/frontend/web/src/services/thunks/session.ts b/invokeai/frontend/web/src/services/thunks/session.ts index 903bb3a2de..c92b303ff7 100644 --- a/invokeai/frontend/web/src/services/thunks/session.ts +++ b/invokeai/frontend/web/src/services/thunks/session.ts @@ -1,11 +1,9 @@ import { createAppAsyncThunk } from 'app/store/storeUtils'; import { SessionsService } from 'services/api'; -import { - buildCanvasGraph, - buildLinearGraph as buildGenerateGraph, -} from 'features/nodes/util/linearGraphBuilder/buildLinearGraph'; +import { buildLinearGraph as buildGenerateGraph } from 'features/nodes/util/buildLinearGraph'; +import { buildCanvasGraph } from 'features/nodes/util/buildCanvasGraph'; import { isAnyOf, isFulfilled } from '@reduxjs/toolkit'; -import { buildNodesGraph } from 'features/nodes/util/nodesGraphBuilder/buildNodesGraph'; +import { buildNodesGraph } from 'features/nodes/util/buildNodesGraph'; import { log } from 'app/logging/useLogger'; import { serializeError } from 'serialize-error';