feat(ui): wip canvas nodes migration 3

This commit is contained in:
psychedelicious 2023-05-03 14:35:47 +10:00
parent cee21ca082
commit f7bbc4004a
11 changed files with 70 additions and 221 deletions

View File

@ -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;
};

View File

@ -29,6 +29,7 @@ import {
isCanvasBaseImage, isCanvasBaseImage,
isCanvasMaskLine, isCanvasMaskLine,
} from './canvasTypes'; } from './canvasTypes';
import { invocationComplete } from 'services/events/actions';
export const initialLayerState: CanvasLayerState = { export const initialLayerState: CanvasLayerState = {
objects: [], objects: [],
@ -289,7 +290,7 @@ export const canvasSlice = createSlice({
state, state,
action: PayloadAction<{ action: PayloadAction<{
boundingBox: IRect; boundingBox: IRect;
image: InvokeAI._Image; image: InvokeAI.Image;
}> }>
) => { ) => {
const { boundingBox, image } = action.payload; const { boundingBox, image } = action.payload;
@ -815,6 +816,11 @@ export const canvasSlice = createSlice({
state.isTransformingBoundingBox = false; state.isTransformingBoundingBox = false;
}, },
}, },
extraReducers(builder) {
builder.addCase(invocationComplete, (state, action) => {
//
});
},
}); });
export const { export const {

View File

@ -162,4 +162,5 @@ export interface CanvasState {
stageDimensions: Dimensions; stageDimensions: Dimensions;
stageScale: number; stageScale: number;
tool: CanvasTool; tool: CanvasTool;
pendingBoundingBox?: IRect;
} }

View File

@ -16,7 +16,7 @@ import { masks } from 'dateformat';
const moduleLog = log.child({ namespace: 'getCanvasDataURLs' }); const moduleLog = log.child({ namespace: 'getCanvasDataURLs' });
export const getCanvasDataURLs = (state: RootState) => { export const getCanvasData = (state: RootState) => {
const canvasBaseLayer = getCanvasBaseLayer(); const canvasBaseLayer = getCanvasBaseLayer();
const canvasStage = getCanvasStage(); const canvasStage = getCanvasStage();
@ -85,7 +85,6 @@ export const getCanvasDataURLs = (state: RootState) => {
offscreenContainer.remove(); offscreenContainer.remove();
if (!maskImageData) { if (!maskImageData) {
moduleLog.error('Unable to get mask stage context');
return; return;
} }

View File

@ -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
);
};

View File

@ -2,7 +2,7 @@ import { Box } from '@chakra-ui/react';
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { memo } from 'react'; import { memo } from 'react';
import { buildNodesGraph } from '../util/nodesGraphBuilder/buildNodesGraph'; import { buildNodesGraph } from '../util/buildNodesGraph';
const NodeGraphOverlay = () => { const NodeGraphOverlay = () => {
const state = useAppSelector((state: RootState) => state); const state = useAppSelector((state: RootState) => state);

View File

@ -1,58 +1,31 @@
import { RootState } from 'app/store/store'; import { RootState } from 'app/store/store';
import { DataURLToImageInvocation, Graph } from 'services/api'; import { DataURLToImageInvocation, Graph } from 'services/api';
import { buildImg2ImgNode } from './buildImageToImageNode'; import { buildImg2ImgNode } from './linearGraphBuilder/buildImageToImageNode';
import { buildTxt2ImgNode } from './buildTextToImageNode'; import { buildTxt2ImgNode } from './linearGraphBuilder/buildTextToImageNode';
import { buildRangeNode } from './buildRangeNode'; import { buildRangeNode } from './linearGraphBuilder/buildRangeNode';
import { buildIterateNode } from './buildIterateNode'; import { buildIterateNode } from './linearGraphBuilder/buildIterateNode';
import { buildEdges } from './buildEdges'; import { buildEdges } from './linearGraphBuilder/buildEdges';
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider'; import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import { getCanvasDataURLs } from 'features/canvas/util/getCanvasDataURLs'; import { getCanvasData } from 'features/canvas/util/getCanvasData';
import { log } from 'console'; import { getNodeType } from './getNodeType';
import { getNodeType } from '../getNodeType';
import { v4 as uuidv4 } from 'uuid'; 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 => { export const buildCanvasGraph = (state: RootState): Graph | undefined => {
// The base node is either a txt2img or img2img node const c = getCanvasData(state);
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);
if (!c) { if (!c) {
throw 'problm creating canvas graph'; moduleLog.error('Unable to create canvas graph');
return;
} }
moduleLog.debug({ data: c }, 'Built canvas data');
const { const {
baseDataURL, baseDataURL,
maskDataURL, maskDataURL,
@ -61,21 +34,13 @@ export const buildCanvasGraph = (state: RootState): Graph => {
doesMaskHaveBlackPixels, doesMaskHaveBlackPixels,
} = c; } = c;
console.log({
baseDataURL,
maskDataURL,
baseIsPartiallyTransparent,
baseIsFullyTransparent,
doesMaskHaveBlackPixels,
});
const nodeType = getNodeType( const nodeType = getNodeType(
baseIsPartiallyTransparent, baseIsPartiallyTransparent,
baseIsFullyTransparent, baseIsFullyTransparent,
doesMaskHaveBlackPixels doesMaskHaveBlackPixels
); );
console.log(nodeType); moduleLog.debug(`Node type ${nodeType}`);
// The base node is either a txt2img or img2img node // The base node is either a txt2img or img2img node
const baseNode = const baseNode =

View File

@ -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;
};

View File

@ -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 (
// <InvokeWorkarea parametersPanelContent={<UnifiedCanvasParameters />}>
// {activeTabName === 'unifiedCanvas' &&
// (shouldUseCanvasBetaLayout ? (
// <UnifiedCanvasContentBeta />
// ) : (
// <UnifiedCanvasContent />
// ))}
// </InvokeWorkarea>
// );
// }
import { Box, Flex } from '@chakra-ui/react'; import { Box, Flex } from '@chakra-ui/react';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { memo } from 'react'; import { memo } from 'react';
@ -34,7 +8,6 @@ import ParametersSlide from '../../common/ParametersSlide';
import UnifiedCanvasParameters from './UnifiedCanvasParameters'; import UnifiedCanvasParameters from './UnifiedCanvasParameters';
import UnifiedCanvasContentBeta from './UnifiedCanvasBeta/UnifiedCanvasContentBeta'; import UnifiedCanvasContentBeta from './UnifiedCanvasBeta/UnifiedCanvasContentBeta';
import UnifiedCanvasContent from './UnifiedCanvasContent'; import UnifiedCanvasContent from './UnifiedCanvasContent';
import { useGetCanvasNodeType } from 'features/canvas/hooks/useGetCanvasNodeType';
const CanvasWorkspace = () => { const CanvasWorkspace = () => {
const shouldPinParametersPanel = useAppSelector( const shouldPinParametersPanel = useAppSelector(

View File

@ -1,11 +1,9 @@
import { createAppAsyncThunk } from 'app/store/storeUtils'; import { createAppAsyncThunk } from 'app/store/storeUtils';
import { SessionsService } from 'services/api'; import { SessionsService } from 'services/api';
import { import { buildLinearGraph as buildGenerateGraph } from 'features/nodes/util/buildLinearGraph';
buildCanvasGraph, import { buildCanvasGraph } from 'features/nodes/util/buildCanvasGraph';
buildLinearGraph as buildGenerateGraph,
} from 'features/nodes/util/linearGraphBuilder/buildLinearGraph';
import { isAnyOf, isFulfilled } from '@reduxjs/toolkit'; 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 { log } from 'app/logging/useLogger';
import { serializeError } from 'serialize-error'; import { serializeError } from 'serialize-error';