diff --git a/frontend/package.json b/frontend/package.json
index 0327ed0f99..5b4ca2c587 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -35,6 +35,7 @@
"react-icons": "^4.4.0",
"react-image-pan-zoom-rotate": "^1.6.0",
"react-konva": "^18.2.3",
+ "react-konva-utils": "^0.3.0",
"react-redux": "^8.0.2",
"react-transition-group": "^4.4.5",
"redux-deep-persist": "^1.0.6",
diff --git a/frontend/src/app/socketio/listeners.ts b/frontend/src/app/socketio/listeners.ts
index fc1472ec5b..3d7e6fb86e 100644
--- a/frontend/src/app/socketio/listeners.ts
+++ b/frontend/src/app/socketio/listeners.ts
@@ -38,7 +38,7 @@ import {
requestSystemConfig,
} from './actions';
import {
- addImageToOutpaintingSesion,
+ addImageToOutpainting,
setImageToInpaint,
} from 'features/canvas/canvasSlice';
import { tabMap } from 'features/tabs/InvokeTabs';
@@ -119,7 +119,7 @@ const makeSocketIOListeners = (
if (data.generationMode === 'outpainting' && data.boundingBox) {
const { boundingBox } = data;
dispatch(
- addImageToOutpaintingSesion({
+ addImageToOutpainting({
image: newImage,
boundingBox,
})
diff --git a/frontend/src/app/store.ts b/frontend/src/app/store.ts
index 39ee82e93e..6b20d53982 100644
--- a/frontend/src/app/store.ts
+++ b/frontend/src/app/store.ts
@@ -94,6 +94,18 @@ export const store = configureStore({
immutableCheck: false,
serializableCheck: false,
}).concat(socketioMiddleware()),
+ devTools: {
+ actionsDenylist: [
+ // 'canvas/setCursorPosition',
+ // 'canvas/setStageCoordinates',
+ // 'canvas/setStageScale',
+ // 'canvas/setIsDrawing',
+ // 'canvas/setBoundingBoxCoordinates',
+ // 'canvas/setBoundingBoxDimensions',
+ // 'canvas/setIsDrawing',
+ // 'canvas/addPointToCurrentLine',
+ ],
+ },
});
export type AppGetState = typeof store.getState;
diff --git a/frontend/src/common/util/parameterTranslation.ts b/frontend/src/common/util/parameterTranslation.ts
index 2d010b3815..c47b8e3c56 100644
--- a/frontend/src/common/util/parameterTranslation.ts
+++ b/frontend/src/common/util/parameterTranslation.ts
@@ -111,7 +111,7 @@ export const frontendToBackendParameters = (
canvasImageLayerRef.current
) {
const {
- objects,
+ layerState: { objects },
boundingBoxCoordinates,
boundingBoxDimensions,
inpaintReplace,
diff --git a/frontend/src/features/canvas/IAICanvas.tsx b/frontend/src/features/canvas/IAICanvas.tsx
index 14c9115332..a7bca897c7 100644
--- a/frontend/src/features/canvas/IAICanvas.tsx
+++ b/frontend/src/features/canvas/IAICanvas.tsx
@@ -9,6 +9,7 @@ import { useAppSelector } from 'app/store';
import {
baseCanvasImageSelector,
currentCanvasSelector,
+ isStagingSelector,
outpaintingCanvasSelector,
} from 'features/canvas/canvasSlice';
@@ -33,17 +34,16 @@ import IAICanvasObjectRenderer from './IAICanvasObjectRenderer';
import IAICanvasGrid from './IAICanvasGrid';
import IAICanvasIntermediateImage from './IAICanvasIntermediateImage';
import IAICanvasStatusText from './IAICanvasStatusText';
-import { Box, Button } from '@chakra-ui/react';
-import { rgbaColorToRgbString, rgbaColorToString } from './util/colorToString';
+import IAICanvasStagingArea from './IAICanvasStagingArea';
const canvasSelector = createSelector(
[
currentCanvasSelector,
outpaintingCanvasSelector,
- baseCanvasImageSelector,
+ isStagingSelector,
activeTabNameSelector,
],
- (currentCanvas, outpaintingCanvas, baseCanvasImage, activeTabName) => {
+ (currentCanvas, outpaintingCanvas, isStaging, activeTabName) => {
const {
isMaskEnabled,
stageScale,
@@ -54,29 +54,23 @@ const canvasSelector = createSelector(
stageDimensions,
stageCoordinates,
tool,
- layer,
- boundingBoxCoordinates,
- boundingBoxDimensions,
isMovingStage,
- maskColor,
} = currentCanvas;
const { shouldShowGrid } = outpaintingCanvas;
let stageCursor: string | undefined = '';
- if (tool === 'move') {
- if (isTransformingBoundingBox) {
- stageCursor = undefined;
- } else if (isMouseOverBoundingBox) {
- stageCursor = 'move';
- } else if (activeTabName === 'outpainting') {
- if (isMovingStage) {
- stageCursor = 'grabbing';
- } else {
- stageCursor = 'grab';
- }
+ if (tool === 'move' || isStaging) {
+ if (isMovingStage) {
+ stageCursor = 'grabbing';
+ } else {
+ stageCursor = 'grab';
}
+ } else if (isTransformingBoundingBox) {
+ stageCursor = undefined;
+ } else if (isMouseOverBoundingBox) {
+ stageCursor = 'move';
} else {
stageCursor = 'none';
}
@@ -91,11 +85,8 @@ const canvasSelector = createSelector(
stageDimensions,
stageScale,
tool,
- layer,
- boundingBoxCoordinates,
- boundingBoxDimensions,
- maskColorString: rgbaColorToString({ ...maskColor, a: 0.5 }),
- outpaintingOnly: activeTabName === 'outpainting',
+ isOnOutpaintingTab: activeTabName === 'outpainting',
+ isStaging,
};
},
{
@@ -120,11 +111,8 @@ const IAICanvas = () => {
stageDimensions,
stageScale,
tool,
- layer,
- outpaintingOnly,
- boundingBoxCoordinates,
- boundingBoxDimensions,
- maskColorString,
+ isOnOutpaintingTab,
+ isStaging,
} = useAppSelector(canvasSelector);
useCanvasHotkeys();
@@ -151,25 +139,15 @@ const IAICanvas = () => {
const { handleDragStart, handleDragMove, handleDragEnd } =
useCanvasDragMove();
- const panelTop = boundingBoxCoordinates.y + boundingBoxDimensions.height;
- const panelLeft = boundingBoxCoordinates.x + boundingBoxDimensions.width;
-
return (
{
onDragMove={handleDragMove}
onDragEnd={handleDragEnd}
onWheel={handleWheel}
- listening={tool === 'move' && !isModifyingBoundingBox}
+ listening={(tool === 'move' || isStaging) && !isModifyingBoundingBox}
draggable={
- tool === 'move' && !isModifyingBoundingBox && outpaintingOnly
+ (tool === 'move' || isStaging) &&
+ !isModifyingBoundingBox &&
+ isOnOutpaintingTab
}
>
@@ -209,14 +189,21 @@ const IAICanvas = () => {
-
-
+ {!isStaging && (
+ <>
+
+
+ >
+ )}
+
+
+ {isStaging && }
- {outpaintingOnly && }
+ {isOnOutpaintingTab && }
);
diff --git a/frontend/src/features/canvas/IAICanvasBrushButtonPopover.tsx b/frontend/src/features/canvas/IAICanvasBrushButtonPopover.tsx
index 860390460a..bbffd98c49 100644
--- a/frontend/src/features/canvas/IAICanvasBrushButtonPopover.tsx
+++ b/frontend/src/features/canvas/IAICanvasBrushButtonPopover.tsx
@@ -1,13 +1,12 @@
import { createSelector } from '@reduxjs/toolkit';
import {
currentCanvasSelector,
- outpaintingCanvasSelector,
+ isStagingSelector,
setBrushColor,
setBrushSize,
setTool,
} from './canvasSlice';
import { useAppDispatch, useAppSelector } from 'app/store';
-import { activeTabNameSelector } from 'features/options/optionsSelectors';
import _ from 'lodash';
import IAIIconButton from 'common/components/IAIIconButton';
import { FaPaintBrush } from 'react-icons/fa';
@@ -17,35 +16,15 @@ import IAISlider from 'common/components/IAISlider';
import { Flex } from '@chakra-ui/react';
export const selector = createSelector(
- [currentCanvasSelector, outpaintingCanvasSelector, activeTabNameSelector],
- (currentCanvas, outpaintingCanvas, activeTabName) => {
- const {
- layer,
- maskColor,
- brushColor,
- brushSize,
- eraserSize,
- tool,
- shouldDarkenOutsideBoundingBox,
- shouldShowIntermediates,
- } = currentCanvas;
-
- const { shouldShowGrid, shouldSnapToGrid, shouldAutoSave } =
- outpaintingCanvas;
+ [currentCanvasSelector, isStagingSelector],
+ (currentCanvas, isStaging) => {
+ const { brushColor, brushSize, tool } = currentCanvas;
return {
- layer,
tool,
- maskColor,
brushColor,
brushSize,
- eraserSize,
- activeTabName,
- shouldShowGrid,
- shouldSnapToGrid,
- shouldAutoSave,
- shouldDarkenOutsideBoundingBox,
- shouldShowIntermediates,
+ isStaging,
};
},
{
@@ -57,7 +36,7 @@ export const selector = createSelector(
const IAICanvasBrushButtonPopover = () => {
const dispatch = useAppDispatch();
- const { tool, brushColor, brushSize } = useAppSelector(selector);
+ const { tool, brushColor, brushSize, isStaging } = useAppSelector(selector);
return (
{
aria-label="Brush (B)"
tooltip="Brush (B)"
icon={}
- data-selected={tool === 'brush'}
+ data-selected={tool === 'brush' && !isStaging}
onClick={() => dispatch(setTool('brush'))}
+ isDisabled={isStaging}
/>
}
>
diff --git a/frontend/src/features/canvas/IAICanvasControls/IAICanvasMaskControls/IAICanvasMaskClear.tsx b/frontend/src/features/canvas/IAICanvasControls/IAICanvasMaskControls/IAICanvasMaskClear.tsx
index 6c0b7c0410..325455b74c 100644
--- a/frontend/src/features/canvas/IAICanvasControls/IAICanvasMaskControls/IAICanvasMaskClear.tsx
+++ b/frontend/src/features/canvas/IAICanvasControls/IAICanvasMaskControls/IAICanvasMaskClear.tsx
@@ -18,9 +18,10 @@ import { useToast } from '@chakra-ui/react';
const canvasMaskClearSelector = createSelector(
[currentCanvasSelector, activeTabNameSelector],
(currentCanvas, activeTabName) => {
- const { isMaskEnabled, objects } = currentCanvas as
- | InpaintingCanvasState
- | OutpaintingCanvasState;
+ const {
+ isMaskEnabled,
+ layerState: { objects },
+ } = currentCanvas as InpaintingCanvasState | OutpaintingCanvasState;
return {
isMaskEnabled,
diff --git a/frontend/src/features/canvas/IAICanvasControls/IAICanvasRedoButton.tsx b/frontend/src/features/canvas/IAICanvasControls/IAICanvasRedoButton.tsx
index 212e9fd87f..67ba63301a 100644
--- a/frontend/src/features/canvas/IAICanvasControls/IAICanvasRedoButton.tsx
+++ b/frontend/src/features/canvas/IAICanvasControls/IAICanvasRedoButton.tsx
@@ -11,10 +11,10 @@ import _ from 'lodash';
const canvasRedoSelector = createSelector(
[currentCanvasSelector, activeTabNameSelector],
(currentCanvas, activeTabName) => {
- const { futureObjects } = currentCanvas;
+ const { futureLayerStates } = currentCanvas;
return {
- canRedo: futureObjects.length > 0,
+ canRedo: futureLayerStates.length > 0,
activeTabName,
};
},
@@ -34,7 +34,7 @@ export default function IAICanvasRedoButton() {
};
useHotkeys(
- ['meta+shift+z', 'control+shift+z', 'control+y', 'meta+y'],
+ ['meta+shift+z', 'ctrl+shift+z', 'control+y', 'meta+y'],
() => {
handleRedo();
},
diff --git a/frontend/src/features/canvas/IAICanvasControls/IAICanvasUndoButton.tsx b/frontend/src/features/canvas/IAICanvasControls/IAICanvasUndoButton.tsx
index 64a60edd02..0582d32637 100644
--- a/frontend/src/features/canvas/IAICanvasControls/IAICanvasUndoButton.tsx
+++ b/frontend/src/features/canvas/IAICanvasControls/IAICanvasUndoButton.tsx
@@ -11,10 +11,10 @@ import { activeTabNameSelector } from 'features/options/optionsSelectors';
const canvasUndoSelector = createSelector(
[currentCanvasSelector, activeTabNameSelector],
(canvas, activeTabName) => {
- const { pastObjects } = canvas;
+ const { pastLayerStates } = canvas;
return {
- canUndo: pastObjects.length > 0,
+ canUndo: pastLayerStates.length > 0,
activeTabName,
};
},
@@ -35,7 +35,7 @@ export default function IAICanvasUndoButton() {
};
useHotkeys(
- ['meta+z', 'control+z'],
+ ['meta+z', 'ctrl+z'],
() => {
handleUndo();
},
diff --git a/frontend/src/features/canvas/IAICanvasEraserButtonPopover.tsx b/frontend/src/features/canvas/IAICanvasEraserButtonPopover.tsx
index ae5ee21125..b36c757b82 100644
--- a/frontend/src/features/canvas/IAICanvasEraserButtonPopover.tsx
+++ b/frontend/src/features/canvas/IAICanvasEraserButtonPopover.tsx
@@ -1,5 +1,10 @@
import { createSelector } from '@reduxjs/toolkit';
-import { currentCanvasSelector, setEraserSize, setTool } from './canvasSlice';
+import {
+ currentCanvasSelector,
+ isStagingSelector,
+ setEraserSize,
+ setTool,
+} from './canvasSlice';
import { useAppDispatch, useAppSelector } from 'app/store';
import _ from 'lodash';
import IAIIconButton from 'common/components/IAIIconButton';
@@ -10,13 +15,14 @@ import { Flex } from '@chakra-ui/react';
import { useHotkeys } from 'react-hotkeys-hook';
export const selector = createSelector(
- [currentCanvasSelector],
- (currentCanvas) => {
+ [currentCanvasSelector, isStagingSelector],
+ (currentCanvas, isStaging) => {
const { eraserSize, tool } = currentCanvas;
return {
tool,
eraserSize,
+ isStaging,
};
},
{
@@ -27,7 +33,7 @@ export const selector = createSelector(
);
const IAICanvasEraserButtonPopover = () => {
const dispatch = useAppDispatch();
- const { tool, eraserSize } = useAppSelector(selector);
+ const { tool, eraserSize, isStaging } = useAppSelector(selector);
const handleSelectEraserTool = () => dispatch(setTool('eraser'));
@@ -51,7 +57,8 @@ const IAICanvasEraserButtonPopover = () => {
aria-label="Eraser (E)"
tooltip="Eraser (E)"
icon={}
- data-selected={tool === 'eraser'}
+ data-selected={tool === 'eraser' && !isStaging}
+ isDisabled={isStaging}
onClick={() => dispatch(setTool('eraser'))}
/>
}
diff --git a/frontend/src/features/canvas/IAICanvasMaskCompositer.tsx b/frontend/src/features/canvas/IAICanvasMaskCompositer.tsx
index 93c91ec619..798b6e85b1 100644
--- a/frontend/src/features/canvas/IAICanvasMaskCompositer.tsx
+++ b/frontend/src/features/canvas/IAICanvasMaskCompositer.tsx
@@ -148,7 +148,17 @@ const IAICanvasMaskCompositer = (props: IAICanvasMaskCompositerProps) => {
return () => clearInterval(timer);
}, []);
- if (!fillPatternImage) return null;
+ if (
+ !(
+ fillPatternImage &&
+ stageCoordinates.x &&
+ stageCoordinates.y &&
+ stageScale &&
+ stageDimensions.width &&
+ stageDimensions.height
+ )
+ )
+ return null;
return (
{
- const { objects } = currentCanvas as
- | InpaintingCanvasState
- | OutpaintingCanvasState;
- return {
- objects,
- };
+ (currentCanvas) => {
+ return currentCanvas.layerState.objects;
},
{
memoizeOptions: {
@@ -37,7 +32,7 @@ type InpaintingCanvasLinesProps = GroupConfig;
*/
const IAICanvasLines = (props: InpaintingCanvasLinesProps) => {
const { ...rest } = props;
- const { objects } = useAppSelector(canvasLinesSelector);
+ const objects = useAppSelector(canvasLinesSelector);
return (
diff --git a/frontend/src/features/canvas/IAICanvasObjectRenderer.tsx b/frontend/src/features/canvas/IAICanvasObjectRenderer.tsx
index a776ff69ee..9187b1f5ec 100644
--- a/frontend/src/features/canvas/IAICanvasObjectRenderer.tsx
+++ b/frontend/src/features/canvas/IAICanvasObjectRenderer.tsx
@@ -1,4 +1,4 @@
-import { createSelector } from '@reduxjs/toolkit';
+import { createSelector, current } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store';
import _ from 'lodash';
import { Group, Line } from 'react-konva';
@@ -13,7 +13,10 @@ import { rgbaColorToString } from './util/colorToString';
const selector = createSelector(
[currentCanvasSelector],
(currentCanvas) => {
- return currentCanvas.objects;
+ const { objects } = currentCanvas.layerState;
+ return {
+ objects,
+ };
},
{
memoizeOptions: {
@@ -23,7 +26,7 @@ const selector = createSelector(
);
const IAICanvasObjectRenderer = () => {
- const objects = useAppSelector(selector);
+ const { objects } = useAppSelector(selector);
if (!objects) return null;
diff --git a/frontend/src/features/canvas/IAICanvasOutpaintingControls.tsx b/frontend/src/features/canvas/IAICanvasOutpaintingControls.tsx
index c3504238d4..99bde3a7a0 100644
--- a/frontend/src/features/canvas/IAICanvasOutpaintingControls.tsx
+++ b/frontend/src/features/canvas/IAICanvasOutpaintingControls.tsx
@@ -2,6 +2,7 @@ import { ButtonGroup } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import {
currentCanvasSelector,
+ isStagingSelector,
resetCanvas,
setTool,
uploadOutpaintingMergedImage,
@@ -27,12 +28,13 @@ import IAICanvasBrushButtonPopover from './IAICanvasBrushButtonPopover';
import IAICanvasMaskButtonPopover from './IAICanvasMaskButtonPopover';
export const canvasControlsSelector = createSelector(
- [currentCanvasSelector],
- (currentCanvas) => {
+ [currentCanvasSelector, isStagingSelector],
+ (currentCanvas, isStaging) => {
const { tool } = currentCanvas;
return {
tool,
+ isStaging,
};
},
{
@@ -44,7 +46,7 @@ export const canvasControlsSelector = createSelector(
const IAICanvasOutpaintingControls = () => {
const dispatch = useAppDispatch();
- const { tool } = useAppSelector(canvasControlsSelector);
+ const { tool, isStaging } = useAppSelector(canvasControlsSelector);
return (
@@ -56,7 +58,7 @@ const IAICanvasOutpaintingControls = () => {
aria-label="Move (M)"
tooltip="Move (M)"
icon={
}
- data-selected={tool === 'move'}
+ data-selected={tool === 'move' || isStaging}
onClick={() => dispatch(setTool('move'))}
/>
diff --git a/frontend/src/features/canvas/IAICanvasStagingArea.tsx b/frontend/src/features/canvas/IAICanvasStagingArea.tsx
new file mode 100644
index 0000000000..fc80a26b07
--- /dev/null
+++ b/frontend/src/features/canvas/IAICanvasStagingArea.tsx
@@ -0,0 +1,164 @@
+import { background, ButtonGroup, ChakraProvider } from '@chakra-ui/react';
+import { CacheProvider } from '@emotion/react';
+import { createSelector } from '@reduxjs/toolkit';
+import { useAppDispatch, useAppSelector } from 'app/store';
+import IAIButton from 'common/components/IAIButton';
+import IAIIconButton from 'common/components/IAIIconButton';
+import { GroupConfig } from 'konva/lib/Group';
+import _ from 'lodash';
+import { emotionCache } from 'main';
+import { useState } from 'react';
+import {
+ FaArrowLeft,
+ FaArrowRight,
+ FaCheck,
+ FaEye,
+ FaEyeSlash,
+ FaTrash,
+} from 'react-icons/fa';
+import { Group, Rect } from 'react-konva';
+import { Html } from 'react-konva-utils';
+import {
+ commitStagingAreaImage,
+ currentCanvasSelector,
+ discardStagedImages,
+ nextStagingAreaImage,
+ prevStagingAreaImage,
+} from './canvasSlice';
+import IAICanvasImage from './IAICanvasImage';
+
+const selector = createSelector(
+ [currentCanvasSelector],
+ (currentCanvas) => {
+ const {
+ layerState: {
+ stagingArea: { images, selectedImageIndex },
+ },
+ } = currentCanvas;
+
+ return {
+ currentStagingAreaImage:
+ images.length > 0 ? images[selectedImageIndex] : undefined,
+ isOnFirstImage: selectedImageIndex === 0,
+ isOnLastImage: selectedImageIndex === images.length - 1,
+ };
+ },
+ {
+ memoizeOptions: {
+ resultEqualityCheck: _.isEqual,
+ },
+ }
+);
+
+type Props = GroupConfig;
+
+const IAICanvasStagingArea = (props: Props) => {
+ const { ...rest } = props;
+ const dispatch = useAppDispatch();
+ const { isOnFirstImage, isOnLastImage, currentStagingAreaImage } =
+ useAppSelector(selector);
+
+ const [shouldShowStagedImage, setShouldShowStagedImage] =
+ useState
(true);
+
+ if (!currentStagingAreaImage) return null;
+
+ const {
+ x,
+ y,
+ image: { width, height, url },
+ } = currentStagingAreaImage;
+
+ return (
+
+
+ {shouldShowStagedImage && }
+
+
+
+
+
+
+
+
+
+ }
+ onClick={() => dispatch(prevStagingAreaImage())}
+ data-selected={true}
+ isDisabled={isOnFirstImage}
+ />
+ }
+ onClick={() => dispatch(nextStagingAreaImage())}
+ data-selected={true}
+ isDisabled={isOnLastImage}
+ />
+ }
+ onClick={() => dispatch(commitStagingAreaImage())}
+ data-selected={true}
+ />
+ : }
+ onClick={() =>
+ setShouldShowStagedImage(!shouldShowStagedImage)
+ }
+ data-selected={true}
+ />
+ }
+ onClick={() => dispatch(discardStagedImages())}
+ data-selected={true}
+ />
+
+
+
+
+
+
+ );
+};
+
+export default IAICanvasStagingArea;
diff --git a/frontend/src/features/canvas/canvasSlice.ts b/frontend/src/features/canvas/canvasSlice.ts
index 529cb605b3..be8f0e2f56 100644
--- a/frontend/src/features/canvas/canvasSlice.ts
+++ b/frontend/src/features/canvas/canvasSlice.ts
@@ -47,6 +47,9 @@ export interface GenericCanvasState {
intermediateImage?: InvokeAI.Image;
shouldShowIntermediates: boolean;
maxHistory: number;
+ layerState: CanvasLayerState;
+ pastLayerStates: CanvasLayerState[];
+ futureLayerStates: CanvasLayerState[];
}
export type CanvasLayer = 'base' | 'mask';
@@ -84,7 +87,19 @@ export type CanvasLine = CanvasAnyLine & {
color?: RgbaColor;
};
-type CanvasObject = CanvasImage | CanvasLine | CanvasMaskLine;
+export type CanvasObject = CanvasImage | CanvasLine | CanvasMaskLine;
+
+export type CanvasLayerState = {
+ objects: CanvasObject[];
+ stagingArea: {
+ x: number;
+ y: number;
+ width: number;
+ height: number;
+ images: CanvasImage[];
+ selectedImageIndex: number;
+ };
+};
// type guards
export const isCanvasMaskLine = (obj: CanvasObject): obj is CanvasMaskLine =>
@@ -102,24 +117,13 @@ export const isCanvasAnyLine = (
export type OutpaintingCanvasState = GenericCanvasState & {
layer: CanvasLayer;
- objects: CanvasObject[];
- pastObjects: CanvasObject[][];
- futureObjects: CanvasObject[][];
shouldShowGrid: boolean;
shouldSnapToGrid: boolean;
shouldAutoSave: boolean;
- stagingArea: {
- images: CanvasImage[];
- selectedImageIndex: number;
- };
};
export type InpaintingCanvasState = GenericCanvasState & {
layer: 'mask';
- objects: CanvasObject[];
- pastObjects: CanvasObject[][];
- futureObjects: CanvasObject[][];
- imageToInpaint?: InvokeAI.Image;
};
export type BaseCanvasState = InpaintingCanvasState | OutpaintingCanvasState;
@@ -133,6 +137,18 @@ export interface CanvasState {
outpainting: OutpaintingCanvasState;
}
+const initialLayerState: CanvasLayerState = {
+ objects: [],
+ stagingArea: {
+ x: -1,
+ y: -1,
+ width: -1,
+ height: -1,
+ images: [],
+ selectedImageIndex: -1,
+ },
+};
+
const initialGenericCanvasState: GenericCanvasState = {
tool: 'brush',
brushColor: { r: 90, g: 90, b: 255, a: 1 },
@@ -164,7 +180,10 @@ const initialGenericCanvasState: GenericCanvasState = {
isMoveStageKeyHeld: false,
shouldShowIntermediates: true,
isMovingStage: false,
- maxHistory: 256,
+ maxHistory: 128,
+ layerState: initialLayerState,
+ futureLayerStates: [],
+ pastLayerStates: [],
};
const initialCanvasState: CanvasState = {
@@ -172,20 +191,10 @@ const initialCanvasState: CanvasState = {
doesCanvasNeedScaling: false,
inpainting: {
layer: 'mask',
- objects: [],
- pastObjects: [],
- futureObjects: [],
...initialGenericCanvasState,
},
outpainting: {
layer: 'base',
- objects: [],
- pastObjects: [],
- futureObjects: [],
- stagingArea: {
- images: [],
- selectedImageIndex: 0,
- },
shouldShowGrid: true,
shouldSnapToGrid: true,
shouldAutoSave: false,
@@ -230,14 +239,13 @@ export const canvasSlice = createSlice({
state[state.currentCanvas].eraserSize = action.payload;
},
clearMask: (state) => {
- state[state.currentCanvas].pastObjects.push(
- state[state.currentCanvas].objects
- );
- state[state.currentCanvas].objects = state[
+ const currentCanvas = state[state.currentCanvas];
+ currentCanvas.pastLayerStates.push(currentCanvas.layerState);
+ currentCanvas.layerState.objects = state[
state.currentCanvas
- ].objects.filter((obj) => !isCanvasMaskLine(obj));
- state[state.currentCanvas].futureObjects = [];
- state[state.currentCanvas].shouldPreserveMaskedArea = false;
+ ].layerState.objects.filter((obj) => !isCanvasMaskLine(obj));
+ currentCanvas.futureLayerStates = [];
+ currentCanvas.shouldPreserveMaskedArea = false;
},
toggleShouldInvertMask: (state) => {
state[state.currentCanvas].shouldPreserveMaskedArea =
@@ -271,9 +279,9 @@ export const canvasSlice = createSlice({
state[state.currentCanvas].cursorPosition = action.payload;
},
clearImageToInpaint: (state) => {
- state.inpainting.imageToInpaint = undefined;
+ // TODO
+ // state.inpainting.imageToInpaint = undefined;
},
-
setImageToOutpaint: (state, action: PayloadAction) => {
const { width: canvasWidth, height: canvasHeight } =
state.outpainting.stageDimensions;
@@ -307,16 +315,20 @@ export const canvasSlice = createSlice({
state.outpainting.boundingBoxDimensions = newDimensions;
state.outpainting.boundingBoxCoordinates = newCoordinates;
- // state.outpainting.imageToInpaint = action.payload;
- state.outpainting.objects = [
- {
- kind: 'image',
- layer: 'base',
- x: 0,
- y: 0,
- image: action.payload,
- },
- ];
+ state.outpainting.pastLayerStates.push(state.outpainting.layerState);
+ state.outpainting.layerState = {
+ ...initialLayerState,
+ objects: [
+ {
+ kind: 'image',
+ layer: 'base',
+ x: 0,
+ y: 0,
+ image: action.payload,
+ },
+ ],
+ };
+ state.outpainting.futureLayerStates = [];
state.doesCanvasNeedScaling = true;
},
setImageToInpaint: (state, action: PayloadAction) => {
@@ -352,16 +364,22 @@ export const canvasSlice = createSlice({
state.inpainting.boundingBoxDimensions = newDimensions;
state.inpainting.boundingBoxCoordinates = newCoordinates;
- // state.inpainting.imageToInpaint = action.payload;
- state.inpainting.objects = [
- {
- kind: 'image',
- layer: 'base',
- x: 0,
- y: 0,
- image: action.payload,
- },
- ];
+ state.inpainting.pastLayerStates.push(state.inpainting.layerState);
+
+ state.inpainting.layerState = {
+ ...initialLayerState,
+ objects: [
+ {
+ kind: 'image',
+ layer: 'base',
+ x: 0,
+ y: 0,
+ image: action.payload,
+ },
+ ],
+ };
+
+ state.outpainting.futureLayerStates = [];
state.doesCanvasNeedScaling = true;
},
setStageDimensions: (state, action: PayloadAction) => {
@@ -487,8 +505,8 @@ export const canvasSlice = createSlice({
state[state.currentCanvas].isDrawing = action.payload;
},
setClearBrushHistory: (state) => {
- state[state.currentCanvas].pastObjects = [];
- state[state.currentCanvas].futureObjects = [];
+ state[state.currentCanvas].pastLayerStates = [];
+ state[state.currentCanvas].futureLayerStates = [];
},
setShouldUseInpaintReplace: (state, action: PayloadAction) => {
state[state.currentCanvas].shouldUseInpaintReplace = action.payload;
@@ -524,7 +542,7 @@ export const canvasSlice = createSlice({
setCurrentCanvas: (state, action: PayloadAction) => {
state.currentCanvas = action.payload;
},
- addImageToOutpaintingSesion: (
+ addImageToOutpainting: (
state,
action: PayloadAction<{
boundingBox: IRect;
@@ -536,23 +554,151 @@ export const canvasSlice = createSlice({
if (!boundingBox || !image) return;
const { x, y } = boundingBox;
+ const { width, height } = image;
+
const currentCanvas = state.outpainting;
- currentCanvas.pastObjects.push([...currentCanvas.objects]);
+ // const {
+ // x: stagingX,
+ // y: stagingY,
+ // width: stagingWidth,
+ // height: stagingHeight,
+ // images: stagedImages,
+ // } = currentCanvas.layerState.stagingArea;
- if (currentCanvas.pastObjects.length > currentCanvas.maxHistory) {
- currentCanvas.pastObjects.shift();
+ currentCanvas.pastLayerStates.push(_.cloneDeep(currentCanvas.layerState));
+
+ if (currentCanvas.pastLayerStates.length > currentCanvas.maxHistory) {
+ currentCanvas.pastLayerStates.shift();
}
- currentCanvas.futureObjects = [];
-
- currentCanvas.objects.push({
+ currentCanvas.layerState.stagingArea.images.push({
kind: 'image',
layer: 'base',
x,
y,
image,
});
+
+ currentCanvas.layerState.stagingArea.selectedImageIndex =
+ currentCanvas.layerState.stagingArea.images.length - 1;
+
+ currentCanvas.futureLayerStates = [];
+
+ // // If the new image is in the staging area region, push it to staging area
+ // if (
+ // x === stagingX &&
+ // y === stagingY &&
+ // width === stagingWidth &&
+ // height === stagingHeight
+ // ) {
+ // console.log('pushing new image to staging area images');
+ // currentCanvas.pastLayerStates.push(
+ // _.cloneDeep(currentCanvas.layerState)
+ // );
+
+ // if (currentCanvas.pastLayerStates.length > currentCanvas.maxHistory) {
+ // currentCanvas.pastLayerStates.shift();
+ // }
+
+ // currentCanvas.layerState.stagingArea.images.push({
+ // kind: 'image',
+ // layer: 'base',
+ // x,
+ // y,
+ // image,
+ // });
+
+ // currentCanvas.layerState.stagingArea.selectedImageIndex =
+ // currentCanvas.layerState.stagingArea.images.length - 1;
+
+ // currentCanvas.futureLayerStates = [];
+ // }
+ // // Else, if the staging area is empty, set it to this image
+ // else if (stagedImages.length === 0) {
+ // console.log('setting staging area image to be this one image');
+ // // add new image to staging area
+ // currentCanvas.pastLayerStates.push(
+ // _.cloneDeep(currentCanvas.layerState)
+ // );
+
+ // if (currentCanvas.pastLayerStates.length > currentCanvas.maxHistory) {
+ // currentCanvas.pastLayerStates.shift();
+ // }
+
+ // currentCanvas.layerState.stagingArea = {
+ // images: [
+ // {
+ // kind: 'image',
+ // layer: 'base',
+ // x,
+ // y,
+ // image,
+ // },
+ // ],
+ // x,
+ // y,
+ // width: image.width,
+ // height: image.height,
+ // selectedImageIndex: 0,
+ // };
+
+ // currentCanvas.futureLayerStates = [];
+ // } else {
+ // // commit the current staging area image & set the new image as the only staging area image
+ // currentCanvas.pastLayerStates.push(
+ // _.cloneDeep(currentCanvas.layerState)
+ // );
+
+ // if (currentCanvas.pastLayerStates.length > currentCanvas.maxHistory) {
+ // currentCanvas.pastLayerStates.shift();
+ // }
+
+ // if (stagedImages.length === 1) {
+ // // commit the current staging area image
+ // console.log('committing current image');
+
+ // const {
+ // x: currentStagedX,
+ // y: currentStagedY,
+ // image: currentStagedImage,
+ // } = stagedImages[0];
+
+ // currentCanvas.layerState.objects.push({
+ // kind: 'image',
+ // layer: 'base',
+ // x: currentStagedX,
+ // y: currentStagedY,
+ // image: currentStagedImage,
+ // });
+ // }
+
+ // console.log('setting staging area to this singel new image');
+ // currentCanvas.layerState.stagingArea = {
+ // images: [
+ // {
+ // kind: 'image',
+ // layer: 'base',
+ // x,
+ // y,
+ // image,
+ // },
+ // ],
+ // x,
+ // y,
+ // width: image.width,
+ // height: image.height,
+ // selectedImageIndex: 0,
+ // };
+
+ // currentCanvas.futureLayerStates = [];
+ // }
+ },
+ discardStagedImages: (state) => {
+ const currentCanvas = state[state.currentCanvas];
+ currentCanvas.layerState.stagingArea = {
+ ...initialLayerState.stagingArea,
+ };
},
addLine: (state, action: PayloadAction) => {
const currentCanvas = state[state.currentCanvas];
@@ -567,13 +713,13 @@ export const canvasSlice = createSlice({
const newColor =
layer === 'base' && tool === 'brush' ? { color: brushColor } : {};
- currentCanvas.pastObjects.push(currentCanvas.objects);
+ currentCanvas.pastLayerStates.push(currentCanvas.layerState);
- if (currentCanvas.pastObjects.length > currentCanvas.maxHistory) {
- currentCanvas.pastObjects.shift();
+ if (currentCanvas.pastLayerStates.length > currentCanvas.maxHistory) {
+ currentCanvas.pastLayerStates.shift();
}
- currentCanvas.objects.push({
+ currentCanvas.layerState.objects.push({
kind: 'line',
layer,
tool,
@@ -582,11 +728,11 @@ export const canvasSlice = createSlice({
...newColor,
});
- currentCanvas.futureObjects = [];
+ currentCanvas.futureLayerStates = [];
},
addPointToCurrentLine: (state, action: PayloadAction) => {
const lastLine =
- state[state.currentCanvas].objects.findLast(isCanvasAnyLine);
+ state[state.currentCanvas].layerState.objects.findLast(isCanvasAnyLine);
if (!lastLine) return;
@@ -594,36 +740,33 @@ export const canvasSlice = createSlice({
},
undo: (state) => {
const currentCanvas = state[state.currentCanvas];
- if (currentCanvas.objects.length === 0) return;
- const newObjects = currentCanvas.pastObjects.pop();
+ const targetState = currentCanvas.pastLayerStates.pop();
- if (!newObjects) return;
+ if (!targetState) return;
- currentCanvas.futureObjects.unshift(currentCanvas.objects);
+ currentCanvas.futureLayerStates.unshift(currentCanvas.layerState);
- if (currentCanvas.futureObjects.length > currentCanvas.maxHistory) {
- currentCanvas.futureObjects.pop();
+ if (currentCanvas.futureLayerStates.length > currentCanvas.maxHistory) {
+ currentCanvas.futureLayerStates.pop();
}
- currentCanvas.objects = newObjects;
+ currentCanvas.layerState = targetState;
},
redo: (state) => {
const currentCanvas = state[state.currentCanvas];
- if (currentCanvas.futureObjects.length === 0) return;
+ const targetState = currentCanvas.futureLayerStates.shift();
- const newObjects = currentCanvas.futureObjects.shift();
+ if (!targetState) return;
- if (!newObjects) return;
+ currentCanvas.pastLayerStates.push(currentCanvas.layerState);
- currentCanvas.pastObjects.push(currentCanvas.objects);
-
- if (currentCanvas.pastObjects.length > currentCanvas.maxHistory) {
- currentCanvas.pastObjects.shift();
+ if (currentCanvas.pastLayerStates.length > currentCanvas.maxHistory) {
+ currentCanvas.pastLayerStates.shift();
}
- currentCanvas.objects = newObjects;
+ currentCanvas.layerState = targetState;
},
setShouldShowGrid: (state, action: PayloadAction) => {
state.outpainting.shouldShowGrid = action.payload;
@@ -641,21 +784,69 @@ export const canvasSlice = createSlice({
state[state.currentCanvas].shouldShowIntermediates = action.payload;
},
resetCanvas: (state) => {
- state[state.currentCanvas].pastObjects.push(
- state[state.currentCanvas].objects
+ state[state.currentCanvas].pastLayerStates.push(
+ state[state.currentCanvas].layerState
);
- state[state.currentCanvas].objects = [];
- state[state.currentCanvas].futureObjects = [];
+ state[state.currentCanvas].layerState = initialLayerState;
+ state[state.currentCanvas].futureLayerStates = [];
+ },
+ nextStagingAreaImage: (state) => {
+ const currentIndex =
+ state.outpainting.layerState.stagingArea.selectedImageIndex;
+ const length = state.outpainting.layerState.stagingArea.images.length;
+
+ state.outpainting.layerState.stagingArea.selectedImageIndex = Math.min(
+ currentIndex + 1,
+ length - 1
+ );
+ },
+ prevStagingAreaImage: (state) => {
+ const currentIndex =
+ state.outpainting.layerState.stagingArea.selectedImageIndex;
+
+ state.outpainting.layerState.stagingArea.selectedImageIndex = Math.max(
+ currentIndex - 1,
+ 0
+ );
+ },
+ commitStagingAreaImage: (state) => {
+ const currentCanvas = state[state.currentCanvas];
+ const { images, selectedImageIndex } =
+ currentCanvas.layerState.stagingArea;
+
+ currentCanvas.pastLayerStates.push(_.cloneDeep(currentCanvas.layerState));
+
+ if (currentCanvas.pastLayerStates.length > currentCanvas.maxHistory) {
+ currentCanvas.pastLayerStates.shift();
+ }
+
+ const { x, y, image } = images[selectedImageIndex];
+
+ currentCanvas.layerState.objects.push({
+ kind: 'image',
+ layer: 'base',
+ x,
+ y,
+ image,
+ });
+
+ currentCanvas.layerState.stagingArea = {
+ ...initialLayerState.stagingArea,
+ };
+
+ currentCanvas.futureLayerStates = [];
},
},
extraReducers: (builder) => {
builder.addCase(uploadOutpaintingMergedImage.fulfilled, (state, action) => {
if (!action.payload) return;
- state.outpainting.pastObjects.push([...state.outpainting.objects]);
- state.outpainting.futureObjects = [];
+ state.outpainting.pastLayerStates.push({
+ ...state.outpainting.layerState,
+ });
+ state.outpainting.futureLayerStates = [];
- state.outpainting.objects = [
+ state.outpainting.layerState.objects = [
{
kind: 'image',
layer: 'base',
@@ -709,13 +900,17 @@ export const {
setIsMoveStageKeyHeld,
setStageCoordinates,
setCurrentCanvas,
- addImageToOutpaintingSesion,
+ addImageToOutpainting,
resetCanvas,
setShouldShowGrid,
setShouldSnapToGrid,
setShouldAutoSave,
setShouldShowIntermediates,
setIsMovingStage,
+ nextStagingAreaImage,
+ prevStagingAreaImage,
+ commitStagingAreaImage,
+ discardStagedImages,
} = canvasSlice.actions;
export default canvasSlice.reducer;
@@ -783,6 +978,10 @@ export const uploadOutpaintingMergedImage = createAsyncThunk(
export const currentCanvasSelector = (state: RootState): BaseCanvasState =>
state.canvas[state.canvas.currentCanvas];
+export const isStagingSelector = (state: RootState): boolean =>
+ state.canvas[state.canvas.currentCanvas].layerState.stagingArea.images
+ .length > 0;
+
export const outpaintingCanvasSelector = (
state: RootState
): OutpaintingCanvasState => state.canvas.outpainting;
@@ -794,6 +993,6 @@ export const inpaintingCanvasSelector = (
export const baseCanvasImageSelector = createSelector(
[currentCanvasSelector],
(currentCanvas) => {
- return currentCanvas.objects.find(isCanvasBaseImage);
+ return currentCanvas.layerState.objects.find(isCanvasBaseImage);
}
);
diff --git a/frontend/src/features/canvas/hooks/useCanvasDragMove.ts b/frontend/src/features/canvas/hooks/useCanvasDragMove.ts
index dedc9f3dc9..268bdf6e75 100644
--- a/frontend/src/features/canvas/hooks/useCanvasDragMove.ts
+++ b/frontend/src/features/canvas/hooks/useCanvasDragMove.ts
@@ -6,17 +6,18 @@ import _ from 'lodash';
import { useCallback } from 'react';
import {
currentCanvasSelector,
+ isStagingSelector,
setIsMovingStage,
setStageCoordinates,
} from '../canvasSlice';
const selector = createSelector(
- [currentCanvasSelector, activeTabNameSelector],
- (canvas, activeTabName) => {
+ [currentCanvasSelector, isStagingSelector, activeTabNameSelector],
+ (canvas, isStaging, activeTabName) => {
const { tool } = canvas;
return {
tool,
-
+ isStaging,
activeTabName,
};
},
@@ -25,24 +26,26 @@ const selector = createSelector(
const useCanvasDrag = () => {
const dispatch = useAppDispatch();
- const { tool, activeTabName } = useAppSelector(selector);
+ const { tool, activeTabName, isStaging } = useAppSelector(selector);
return {
handleDragStart: useCallback(() => {
- if (tool !== 'move' || activeTabName !== 'outpainting') return;
+ if (!(tool === 'move' || isStaging)) return;
dispatch(setIsMovingStage(true));
- }, [activeTabName, dispatch, tool]),
+ }, [dispatch, isStaging, tool]),
+
handleDragMove: useCallback(
(e: KonvaEventObject) => {
- if (tool !== 'move' || activeTabName !== 'outpainting') return;
+ if (!(tool === 'move' || isStaging)) return;
dispatch(setStageCoordinates(e.target.getPosition()));
},
- [activeTabName, dispatch, tool]
+ [dispatch, isStaging, tool]
),
+
handleDragEnd: useCallback(() => {
- if (tool !== 'move' || activeTabName !== 'outpainting') return;
+ if (!(tool === 'move' || isStaging)) return;
dispatch(setIsMovingStage(false));
- }, [activeTabName, dispatch, tool]),
+ }, [dispatch, isStaging, tool]),
};
};
diff --git a/frontend/src/features/canvas/hooks/useCanvasMouseDown.ts b/frontend/src/features/canvas/hooks/useCanvasMouseDown.ts
index 503daeaf77..ae72b1686f 100644
--- a/frontend/src/features/canvas/hooks/useCanvasMouseDown.ts
+++ b/frontend/src/features/canvas/hooks/useCanvasMouseDown.ts
@@ -8,18 +8,20 @@ import { MutableRefObject, useCallback } from 'react';
import {
addLine,
currentCanvasSelector,
+ isStagingSelector,
setIsDrawing,
setIsMovingStage,
} from '../canvasSlice';
import getScaledCursorPosition from '../util/getScaledCursorPosition';
const selector = createSelector(
- [activeTabNameSelector, currentCanvasSelector],
- (activeTabName, currentCanvas) => {
+ [activeTabNameSelector, currentCanvasSelector, isStagingSelector],
+ (activeTabName, currentCanvas, isStaging) => {
const { tool } = currentCanvas;
return {
tool,
activeTabName,
+ isStaging,
};
},
{ memoizeOptions: { resultEqualityCheck: _.isEqual } }
@@ -27,14 +29,15 @@ const selector = createSelector(
const useCanvasMouseDown = (stageRef: MutableRefObject) => {
const dispatch = useAppDispatch();
- const { tool } = useAppSelector(selector);
+ const { tool, isStaging } = useAppSelector(selector);
return useCallback(
(e: KonvaEventObject) => {
if (!stageRef.current) return;
+
stageRef.current.container().focus();
- if (tool === 'move') {
+ if (tool === 'move' || isStaging) {
dispatch(setIsMovingStage(true));
return;
}
@@ -50,7 +53,7 @@ const useCanvasMouseDown = (stageRef: MutableRefObject) => {
// Add a new line starting from the current cursor position.
dispatch(addLine([scaledCursorPosition.x, scaledCursorPosition.y]));
},
- [stageRef, dispatch, tool]
+ [stageRef, tool, isStaging, dispatch]
);
};
diff --git a/frontend/src/features/canvas/hooks/useCanvasMouseEnter.ts b/frontend/src/features/canvas/hooks/useCanvasMouseEnter.ts
index 73231f911e..997faa058b 100644
--- a/frontend/src/features/canvas/hooks/useCanvasMouseEnter.ts
+++ b/frontend/src/features/canvas/hooks/useCanvasMouseEnter.ts
@@ -5,16 +5,22 @@ import Konva from 'konva';
import { KonvaEventObject } from 'konva/lib/Node';
import _ from 'lodash';
import { MutableRefObject, useCallback } from 'react';
-import { addLine, currentCanvasSelector, setIsDrawing } from '../canvasSlice';
+import {
+ addLine,
+ currentCanvasSelector,
+ isStagingSelector,
+ setIsDrawing,
+} from '../canvasSlice';
import getScaledCursorPosition from '../util/getScaledCursorPosition';
const selector = createSelector(
- [activeTabNameSelector, currentCanvasSelector],
- (activeTabName, currentCanvas) => {
+ [activeTabNameSelector, currentCanvasSelector, isStagingSelector],
+ (activeTabName, currentCanvas, isStaging) => {
const { tool } = currentCanvas;
return {
tool,
activeTabName,
+ isStaging,
};
},
{ memoizeOptions: { resultEqualityCheck: _.isEqual } }
@@ -24,7 +30,7 @@ const useCanvasMouseEnter = (
stageRef: MutableRefObject
) => {
const dispatch = useAppDispatch();
- const { tool } = useAppSelector(selector);
+ const { tool, isStaging } = useAppSelector(selector);
return useCallback(
(e: KonvaEventObject) => {
@@ -34,14 +40,14 @@ const useCanvasMouseEnter = (
const scaledCursorPosition = getScaledCursorPosition(stageRef.current);
- if (!scaledCursorPosition || tool === 'move') return;
+ if (!scaledCursorPosition || tool === 'move' || isStaging) return;
dispatch(setIsDrawing(true));
// Add a new line starting from the current cursor position.
dispatch(addLine([scaledCursorPosition.x, scaledCursorPosition.y]));
},
- [stageRef, tool, dispatch]
+ [stageRef, tool, isStaging, dispatch]
);
};
diff --git a/frontend/src/features/canvas/hooks/useCanvasMouseMove.ts b/frontend/src/features/canvas/hooks/useCanvasMouseMove.ts
index aa4cbd9557..8519e8e9ab 100644
--- a/frontend/src/features/canvas/hooks/useCanvasMouseMove.ts
+++ b/frontend/src/features/canvas/hooks/useCanvasMouseMove.ts
@@ -9,18 +9,20 @@ import {
addPointToCurrentLine,
currentCanvasSelector,
GenericCanvasState,
+ isStagingSelector,
setCursorPosition,
} from '../canvasSlice';
import getScaledCursorPosition from '../util/getScaledCursorPosition';
const selector = createSelector(
- [activeTabNameSelector, currentCanvasSelector],
- (activeTabName, canvas: GenericCanvasState) => {
- const { tool, isDrawing } = canvas;
+ [activeTabNameSelector, currentCanvasSelector, isStagingSelector],
+ (activeTabName, currentCanvas, isStaging) => {
+ const { tool, isDrawing } = currentCanvas;
return {
tool,
isDrawing,
activeTabName,
+ isStaging,
};
},
{ memoizeOptions: { resultEqualityCheck: _.isEqual } }
@@ -32,7 +34,7 @@ const useCanvasMouseMove = (
lastCursorPositionRef: MutableRefObject
) => {
const dispatch = useAppDispatch();
- const { isDrawing, tool } = useAppSelector(selector);
+ const { isDrawing, tool, isStaging } = useAppSelector(selector);
return useCallback(() => {
if (!stageRef.current) return;
@@ -45,7 +47,7 @@ const useCanvasMouseMove = (
lastCursorPositionRef.current = scaledCursorPosition;
- if (!isDrawing || tool === 'move') return;
+ if (!isDrawing || tool === 'move' || isStaging) return;
didMouseMoveRef.current = true;
dispatch(
@@ -55,6 +57,7 @@ const useCanvasMouseMove = (
didMouseMoveRef,
dispatch,
isDrawing,
+ isStaging,
lastCursorPositionRef,
stageRef,
tool,
diff --git a/frontend/src/features/canvas/hooks/useCanvasMouseUp.ts b/frontend/src/features/canvas/hooks/useCanvasMouseUp.ts
index 685d43b3e2..64c6d68da1 100644
--- a/frontend/src/features/canvas/hooks/useCanvasMouseUp.ts
+++ b/frontend/src/features/canvas/hooks/useCanvasMouseUp.ts
@@ -9,19 +9,21 @@ import {
addPointToCurrentLine,
currentCanvasSelector,
GenericCanvasState,
+ isStagingSelector,
setIsDrawing,
setIsMovingStage,
} from '../canvasSlice';
import getScaledCursorPosition from '../util/getScaledCursorPosition';
const selector = createSelector(
- [activeTabNameSelector, currentCanvasSelector],
- (activeTabName, canvas: GenericCanvasState) => {
- const { tool, isDrawing } = canvas;
+ [activeTabNameSelector, currentCanvasSelector, isStagingSelector],
+ (activeTabName, currentCanvas, isStaging) => {
+ const { tool, isDrawing } = currentCanvas;
return {
tool,
isDrawing,
activeTabName,
+ isStaging,
};
},
{ memoizeOptions: { resultEqualityCheck: _.isEqual } }
@@ -32,10 +34,10 @@ const useCanvasMouseUp = (
didMouseMoveRef: MutableRefObject
) => {
const dispatch = useAppDispatch();
- const { tool, isDrawing } = useAppSelector(selector);
+ const { tool, isDrawing, isStaging } = useAppSelector(selector);
return useCallback(() => {
- if (tool === 'move') {
+ if (tool === 'move' || isStaging) {
dispatch(setIsMovingStage(false));
return;
}
@@ -58,7 +60,7 @@ const useCanvasMouseUp = (
didMouseMoveRef.current = false;
}
dispatch(setIsDrawing(false));
- }, [didMouseMoveRef, dispatch, isDrawing, stageRef, tool]);
+ }, [didMouseMoveRef, dispatch, isDrawing, isStaging, stageRef, tool]);
};
export default useCanvasMouseUp;
diff --git a/frontend/src/features/options/AdvancedOptions/Inpainting/ClearBrushHistory.tsx b/frontend/src/features/options/AdvancedOptions/Inpainting/ClearBrushHistory.tsx
index 4f8307b367..993349990e 100644
--- a/frontend/src/features/options/AdvancedOptions/Inpainting/ClearBrushHistory.tsx
+++ b/frontend/src/features/options/AdvancedOptions/Inpainting/ClearBrushHistory.tsx
@@ -13,12 +13,12 @@ import _ from 'lodash';
const clearBrushHistorySelector = createSelector(
currentCanvasSelector,
(currentCanvas) => {
- const { pastObjects, futureObjects } = currentCanvas as
+ const { pastLayerStates, futureLayerStates } = currentCanvas as
| InpaintingCanvasState
| OutpaintingCanvasState;
return {
mayClearBrushHistory:
- futureObjects.length > 0 || pastObjects.length > 0 ? false : true,
+ futureLayerStates.length > 0 || pastLayerStates.length > 0 ? false : true,
};
},
{
diff --git a/frontend/src/features/tabs/CanvasWorkarea.scss b/frontend/src/features/tabs/CanvasWorkarea.scss
index d3921b7a5f..f83d8f8441 100644
--- a/frontend/src/features/tabs/CanvasWorkarea.scss
+++ b/frontend/src/features/tabs/CanvasWorkarea.scss
@@ -69,10 +69,13 @@
}
.inpainting-canvas-stage {
- // border-radius: 0.5rem;
- // border: 1px solid var(--border-color-light);
+ outline: none;
+ border-radius: 0.5rem;
+ border: 1px solid var(--border-color-light);
+ overflow: hidden;
canvas {
+ outline: none;
border-radius: 0.5rem;
}
}
diff --git a/frontend/src/features/tabs/Outpainting/OutpaintingDisplay.tsx b/frontend/src/features/tabs/Outpainting/OutpaintingDisplay.tsx
index e5fd54f008..47ff66111c 100644
--- a/frontend/src/features/tabs/Outpainting/OutpaintingDisplay.tsx
+++ b/frontend/src/features/tabs/Outpainting/OutpaintingDisplay.tsx
@@ -17,7 +17,9 @@ const outpaintingDisplaySelector = createSelector(
(canvas: CanvasState) => {
const {
doesCanvasNeedScaling,
- outpainting: { objects },
+ outpainting: {
+ layerState: { objects },
+ },
} = canvas;
return {
doesCanvasNeedScaling,
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index 18ab6a62e2..02350c73bd 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -13,7 +13,7 @@ export const persistor = persistStore(store);
import Loading from './Loading';
import App from './app/App';
-const emotionCache = createCache({
+export const emotionCache = createCache({
key: 'invokeai-style-cache',
prepend: true,
});
diff --git a/frontend/yarn.lock b/frontend/yarn.lock
index bb5bac14de..8477d67ffb 100644
--- a/frontend/yarn.lock
+++ b/frontend/yarn.lock
@@ -3420,7 +3420,15 @@ react-is@^18.0.0:
resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b"
integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==
-react-konva@^18.2.3:
+react-konva-utils@^0.3.0:
+ version "0.3.0"
+ resolved "https://registry.yarnpkg.com/react-konva-utils/-/react-konva-utils-0.3.0.tgz#d9099ad1a767286b24fb1b08377af2dfa5c9f176"
+ integrity sha512-yH5FVpDGQ8gHeClyHY533M4oSLjEfYuvn+Z29zXm9osjhuulhtJrh5k+wtyY6QSC0MG0ioqE0cjiudGl1WGB9A==
+ dependencies:
+ react-konva "^18.0.0-0"
+ use-image "^1.0.12"
+
+react-konva@^18.0.0-0, react-konva@^18.2.3:
version "18.2.3"
resolved "https://registry.yarnpkg.com/react-konva/-/react-konva-18.2.3.tgz#75c658fca493bdf515b38f2a8d544fa7a9c754c4"
integrity sha512-OPxjBTgaEGU9pt/VJSVM7QNXYHEZ5CkulX+4fTTvbaH+Wh+vMLbXmH3yjWw4kT/5Qi6t0UQKHPPmirCv8/9sdg==
@@ -3942,7 +3950,7 @@ use-callback-ref@^1.3.0:
dependencies:
tslib "^2.0.0"
-use-image@^1.1.0:
+use-image@^1.0.12, use-image@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/use-image/-/use-image-1.1.0.tgz#dc244c34506d3cf3a8177c1f0bbfb158b9beefe5"
integrity sha512-+cBHRR/44ZyMUS873O0vbVylgMM0AbdTunEplAWXvIQ2p69h2sIo2Qq74zeUsq6AMo+27e5lERQvXzd1crGiMg==