mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): wip canvas nodes migration 3
This commit is contained in:
parent
cee21ca082
commit
f7bbc4004a
@ -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;
|
|
||||||
};
|
|
@ -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 {
|
||||||
|
@ -162,4 +162,5 @@ export interface CanvasState {
|
|||||||
stageDimensions: Dimensions;
|
stageDimensions: Dimensions;
|
||||||
stageScale: number;
|
stageScale: number;
|
||||||
tool: CanvasTool;
|
tool: CanvasTool;
|
||||||
|
pendingBoundingBox?: IRect;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
@ -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
|
|
||||||
);
|
|
||||||
};
|
|
@ -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);
|
||||||
|
@ -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 =
|
@ -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;
|
||||||
|
};
|
@ -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(
|
||||||
|
@ -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';
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user