Organises features/canvas

This commit is contained in:
psychedelicious 2022-11-17 16:25:50 +11:00 committed by blessedcoolant
parent 48ad0c289c
commit 827f516baf
69 changed files with 304 additions and 1167 deletions

View File

@ -5,7 +5,7 @@ import { activeTabNameSelector } from 'features/options/optionsSelectors';
import { OptionsState } from 'features/options/optionsSlice';
import { SystemState } from 'features/system/systemSlice';
import { validateSeedWeights } from 'common/util/seedWeightPairs';
import { initialCanvasImageSelector } from 'features/canvas/canvasSlice';
import { initialCanvasImageSelector } from 'features/canvas/store/canvasSelectors';
export const readinessSelector = createSelector(
[

View File

@ -13,14 +13,12 @@ import {
import { OptionsState } from 'features/options/optionsSlice';
import {
addLogEntry,
errorOccurred,
modelChangeRequested,
setIsProcessing,
} from 'features/system/systemSlice';
import { InvokeTabName } from 'features/tabs/InvokeTabs';
import * as InvokeAI from 'app/invokeai';
import { RootState } from 'app/store';
import { initialCanvasImageSelector } from 'features/canvas/canvasSlice';
/**
* Returns an object containing all functions which use `socketio.emit()`.
@ -71,8 +69,8 @@ const makeSocketIOEmitters = (
// }
// frontendToBackendParametersConfig.imageToProcessUrl = imageUrl;
// } else
// } else
if (!['txt2img', 'img2img'].includes(generationMode)) {
if (!galleryState.currentImage?.url) return;

View File

@ -37,7 +37,7 @@ import {
requestNewImages,
requestSystemConfig,
} from './actions';
import { addImageToStagingArea } from 'features/canvas/canvasSlice';
import { addImageToStagingArea } from 'features/canvas/store/canvasSlice';
import { tabMap } from 'features/tabs/InvokeTabs';
/**

View File

@ -10,7 +10,7 @@ import { getPersistConfig } from 'redux-deep-persist';
import optionsReducer from 'features/options/optionsSlice';
import galleryReducer from 'features/gallery/gallerySlice';
import systemReducer from 'features/system/systemSlice';
import canvasReducer from 'features/canvas/canvasSlice';
import canvasReducer from 'features/canvas/store/canvasSlice';
import { socketioMiddleware } from './socketio/middleware';

View File

@ -5,9 +5,9 @@ import { SystemState } from 'features/system/systemSlice';
import { stringToSeedWeightsArray } from './seedWeightPairs';
import randomInt from './randomInt';
import { InvokeTabName } from 'features/tabs/InvokeTabs';
import { CanvasState, isCanvasMaskLine } from 'features/canvas/canvasSlice';
import { CanvasState, isCanvasMaskLine } from 'features/canvas/store/canvasTypes';
import generateMask from 'features/canvas/util/generateMask';
import { canvasImageLayerRef } from 'features/canvas/IAICanvas';
import { canvasImageLayerRef } from 'features/canvas/components/IAICanvas';
import openBase64ImageInTab from './openBase64ImageInTab';
export type FrontendToBackendParametersConfig = {

View File

@ -1,63 +0,0 @@
import IAICanvasBrushControl from './IAICanvasControls/IAICanvasBrushControl';
import IAICanvasEraserControl from './IAICanvasControls/IAICanvasEraserControl';
import IAICanvasUndoControl from './IAICanvasControls/IAICanvasUndoButton';
import IAICanvasRedoControl from './IAICanvasControls/IAICanvasRedoButton';
import { ButtonGroup } from '@chakra-ui/react';
import IAICanvasMaskClear from './IAICanvasControls/IAICanvasMaskControls/IAICanvasMaskClear';
import IAICanvasMaskVisibilityControl from './IAICanvasControls/IAICanvasMaskControls/IAICanvasMaskVisibilityControl';
import IAICanvasMaskInvertControl from './IAICanvasControls/IAICanvasMaskControls/IAICanvasMaskInvertControl';
import IAICanvasLockBoundingBoxControl from './IAICanvasControls/IAICanvasLockBoundingBoxControl';
import IAICanvasShowHideBoundingBoxControl from './IAICanvasControls/IAICanvasShowHideBoundingBoxControl';
import ImageUploaderIconButton from 'common/components/ImageUploaderIconButton';
import { createSelector } from '@reduxjs/toolkit';
import { RootState } from 'app/store';
import { activeTabNameSelector } from 'features/options/optionsSelectors';
import { OptionsState } from 'features/options/optionsSlice';
import _ from 'lodash';
import { canvasSelector } from './canvasSlice';
export const canvasControlsSelector = createSelector(
[(state: RootState) => state.options, canvasSelector, activeTabNameSelector],
(options: OptionsState, canvas, activeTabName) => {
const { stageScale, boundingBoxCoordinates, boundingBoxDimensions } =
canvas;
return {
activeTabName,
stageScale,
boundingBoxCoordinates,
boundingBoxDimensions,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
const IAICanvasControls = () => {
return (
<div className="inpainting-settings">
<ButtonGroup isAttached={true}>
<IAICanvasBrushControl />
<IAICanvasEraserControl />
</ButtonGroup>
<ButtonGroup isAttached={true}>
<IAICanvasMaskVisibilityControl />
<IAICanvasMaskInvertControl />
<IAICanvasLockBoundingBoxControl />
<IAICanvasShowHideBoundingBoxControl />
<IAICanvasMaskClear />
</ButtonGroup>
<ButtonGroup isAttached={true}>
<IAICanvasUndoControl />
<IAICanvasRedoControl />
</ButtonGroup>
<ButtonGroup isAttached={true}>
<ImageUploaderIconButton />
</ButtonGroup>
</div>
);
};
export default IAICanvasControls;

View File

@ -1,134 +0,0 @@
import { createSelector } from '@reduxjs/toolkit';
import { useHotkeys } from 'react-hotkeys-hook';
import { FaPaintBrush } from 'react-icons/fa';
import { useAppDispatch, useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import IAINumberInput from 'common/components/IAINumberInput';
import IAIPopover from 'common/components/IAIPopover';
import IAISlider from 'common/components/IAISlider';
import { activeTabNameSelector } from 'features/options/optionsSelectors';
import {
canvasSelector,
setBrushSize,
setShouldShowBrushPreview,
setTool,
} from 'features/canvas/canvasSlice';
import _ from 'lodash';
import IAICanvasMaskColorPicker from './IAICanvasMaskControls/IAICanvasMaskColorPicker';
const selector = createSelector(
[canvasSelector, activeTabNameSelector],
(canvas, activeTabName) => {
const { tool, brushSize } = canvas;
return {
tool,
brushSize,
activeTabName,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
export default function IAICanvasBrushControl() {
const dispatch = useAppDispatch();
const { tool, brushSize, activeTabName } = useAppSelector(selector);
const handleSelectBrushTool = () => dispatch(setTool('brush'));
const handleShowBrushPreview = () => {
dispatch(setShouldShowBrushPreview(true));
};
const handleHideBrushPreview = () => {
dispatch(setShouldShowBrushPreview(false));
};
const handleChangeBrushSize = (v: number) => {
dispatch(setShouldShowBrushPreview(true));
dispatch(setBrushSize(v));
};
useHotkeys(
'[',
(e: KeyboardEvent) => {
e.preventDefault();
if (brushSize - 5 > 0) {
handleChangeBrushSize(brushSize - 5);
} else {
handleChangeBrushSize(1);
}
},
{
enabled: true,
},
[activeTabName, brushSize]
);
// Increase brush size
useHotkeys(
']',
(e: KeyboardEvent) => {
e.preventDefault();
handleChangeBrushSize(brushSize + 5);
},
{
enabled: true,
},
[activeTabName, brushSize]
);
// Set tool to brush
useHotkeys(
'b',
(e: KeyboardEvent) => {
e.preventDefault();
handleSelectBrushTool();
},
{
enabled: true,
},
[activeTabName]
);
return (
<IAIPopover
trigger="hover"
onOpen={handleShowBrushPreview}
onClose={handleHideBrushPreview}
triggerComponent={
<IAIIconButton
aria-label="Brush (B)"
tooltip="Brush (B)"
icon={<FaPaintBrush />}
onClick={handleSelectBrushTool}
data-selected={tool === 'brush'}
/>
}
>
<div className="inpainting-brush-options">
<IAISlider
label="Brush Size"
value={brushSize}
onChange={handleChangeBrushSize}
min={1}
max={200}
/>
<IAINumberInput
value={brushSize}
onChange={handleChangeBrushSize}
width={'80px'}
min={1}
max={999}
/>
<IAICanvasMaskColorPicker />
</div>
</IAIPopover>
);
}

View File

@ -1,61 +0,0 @@
import { createSelector } from '@reduxjs/toolkit';
import { useHotkeys } from 'react-hotkeys-hook';
import { FaEraser } from 'react-icons/fa';
import { useAppDispatch, useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import { canvasSelector, setTool } from 'features/canvas/canvasSlice';
import _ from 'lodash';
import { activeTabNameSelector } from 'features/options/optionsSelectors';
const eraserSelector = createSelector(
[canvasSelector, activeTabNameSelector],
(canvas, activeTabName) => {
const { tool, isMaskEnabled } = canvas;
return {
tool,
isMaskEnabled,
activeTabName,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
export default function IAICanvasEraserControl() {
const { tool, isMaskEnabled, activeTabName } = useAppSelector(eraserSelector);
const dispatch = useAppDispatch();
const handleSelectEraserTool = () => dispatch(setTool('eraser'));
// Hotkeys
// Set tool to maskEraser
useHotkeys(
'e',
(e: KeyboardEvent) => {
e.preventDefault();
handleSelectEraserTool();
},
{
enabled: true,
},
[activeTabName]
);
return (
<IAIIconButton
aria-label={
activeTabName === 'inpainting' ? 'Eraser (E)' : 'Erase Mask (E)'
}
tooltip={activeTabName === 'inpainting' ? 'Eraser (E)' : 'Erase Mask (E)'}
icon={<FaEraser />}
onClick={handleSelectEraserTool}
data-selected={tool === 'eraser'}
isDisabled={!isMaskEnabled}
/>
);
}

View File

@ -1,60 +0,0 @@
import { createSelector } from '@reduxjs/toolkit';
import { useHotkeys } from 'react-hotkeys-hook';
import { useAppDispatch, useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import { canvasSelector, setTool } from 'features/canvas/canvasSlice';
import _ from 'lodash';
import { activeTabNameSelector } from 'features/options/optionsSelectors';
import { BsEraser } from 'react-icons/bs';
const imageEraserSelector = createSelector(
[canvasSelector, activeTabNameSelector],
(canvas, activeTabName) => {
const { tool, isMaskEnabled } = canvas;
return {
tool,
isMaskEnabled,
activeTabName,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
export default function IAICanvasImageEraserControl() {
const { tool, isMaskEnabled, activeTabName } =
useAppSelector(imageEraserSelector);
const dispatch = useAppDispatch();
const handleSelectEraserTool = () => dispatch(setTool('eraser'));
// Hotkeys
useHotkeys(
'shift+e',
(e: KeyboardEvent) => {
e.preventDefault();
handleSelectEraserTool();
},
{
enabled: true,
},
[activeTabName, isMaskEnabled]
);
return (
<IAIIconButton
aria-label="Erase Canvas (Shift+E)"
tooltip="Erase Canvas (Shift+E)"
icon={<BsEraser />}
fontSize={18}
onClick={handleSelectEraserTool}
data-selected={tool === 'eraser'}
isDisabled={!isMaskEnabled}
/>
);
}

View File

@ -1,46 +0,0 @@
import { FaLock, FaUnlock } from 'react-icons/fa';
import { useAppDispatch, useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import {
canvasSelector,
setShouldLockBoundingBox,
} from 'features/canvas/canvasSlice';
import { createSelector } from '@reduxjs/toolkit';
import _ from 'lodash';
const canvasLockBoundingBoxSelector = createSelector(
canvasSelector,
(canvas) => {
const { shouldLockBoundingBox } = canvas;
return {
shouldLockBoundingBox,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
const IAICanvasLockBoundingBoxControl = () => {
const dispatch = useAppDispatch();
const { shouldLockBoundingBox } = useAppSelector(
canvasLockBoundingBoxSelector
);
return (
<IAIIconButton
aria-label="Lock Inpainting Box"
tooltip="Lock Inpainting Box"
icon={shouldLockBoundingBox ? <FaLock /> : <FaUnlock />}
data-selected={shouldLockBoundingBox}
onClick={() => {
dispatch(setShouldLockBoundingBox(!shouldLockBoundingBox));
}}
/>
);
};
export default IAICanvasLockBoundingBoxControl;

View File

@ -1,38 +0,0 @@
import { useState } from 'react';
import { FaMask } from 'react-icons/fa';
import IAIPopover from 'common/components/IAIPopover';
import IAIIconButton from 'common/components/IAIIconButton';
import IAICanvasMaskInvertControl from './IAICanvasMaskControls/IAICanvasMaskInvertControl';
import IAICanvasMaskVisibilityControl from './IAICanvasMaskControls/IAICanvasMaskVisibilityControl';
import IAICanvasMaskColorPicker from './IAICanvasMaskControls/IAICanvasMaskColorPicker';
export default function IAICanvasMaskControl() {
const [maskOptionsOpen, setMaskOptionsOpen] = useState<boolean>(false);
return (
<>
<IAIPopover
trigger="hover"
onOpen={() => setMaskOptionsOpen(true)}
onClose={() => setMaskOptionsOpen(false)}
triggerComponent={
<IAIIconButton
aria-label="Mask Options"
tooltip="Mask Options"
icon={<FaMask />}
cursor={'pointer'}
data-selected={maskOptionsOpen}
/>
}
>
<div className="inpainting-button-dropdown">
<IAICanvasMaskVisibilityControl />
<IAICanvasMaskInvertControl />
<IAICanvasMaskColorPicker />
</div>
</IAIPopover>
</>
);
}

View File

@ -1,71 +0,0 @@
import { RgbaColor } from 'react-colorful';
import { useAppDispatch, useAppSelector } from 'app/store';
import IAIColorPicker from 'common/components/IAIColorPicker';
import { canvasSelector, setMaskColor } from 'features/canvas/canvasSlice';
import _ from 'lodash';
import { createSelector } from '@reduxjs/toolkit';
import { activeTabNameSelector } from 'features/options/optionsSelectors';
import { useHotkeys } from 'react-hotkeys-hook';
const selector = createSelector(
[canvasSelector, activeTabNameSelector],
(canvas, activeTabName) => {
const { brushColor } = canvas;
return {
brushColor,
activeTabName,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
export default function IAICanvasBrushColorPicker() {
const dispatch = useAppDispatch();
const { brushColor, activeTabName } = useAppSelector(selector);
const handleChangeBrushColor = (newColor: RgbaColor) => {
dispatch(setMaskColor(newColor));
};
// Decrease brush opacity
useHotkeys(
'shift+[',
(e: KeyboardEvent) => {
e.preventDefault();
handleChangeBrushColor({
...brushColor,
a: Math.max(brushColor.a - 0.05, 0),
});
},
{
enabled: true,
},
[activeTabName, brushColor.a]
);
// Increase brush opacity
useHotkeys(
'shift+]',
(e: KeyboardEvent) => {
e.preventDefault();
handleChangeBrushColor({
...brushColor,
a: Math.min(brushColor.a + 0.05, 1),
});
},
{
enabled: true,
},
[activeTabName, brushColor.a]
);
return (
<IAIColorPicker color={brushColor} onChange={handleChangeBrushColor} />
);
}

View File

@ -1,76 +0,0 @@
import { createSelector } from '@reduxjs/toolkit';
import { FaPlus } from 'react-icons/fa';
import { useAppDispatch, useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import { activeTabNameSelector } from 'features/options/optionsSelectors';
import {
clearMask,
canvasSelector,
isCanvasMaskLine,
} from 'features/canvas/canvasSlice';
import _ from 'lodash';
import { useHotkeys } from 'react-hotkeys-hook';
import { useToast } from '@chakra-ui/react';
const canvasMaskClearSelector = createSelector(
[canvasSelector, activeTabNameSelector],
(canvas, activeTabName) => {
const {
isMaskEnabled,
layerState: { objects },
} = canvas;
return {
isMaskEnabled,
activeTabName,
isMaskEmpty: objects.filter(isCanvasMaskLine).length === 0,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
export default function IAICanvasMaskClear() {
const { isMaskEnabled, activeTabName, isMaskEmpty } = useAppSelector(
canvasMaskClearSelector
);
const dispatch = useAppDispatch();
const toast = useToast();
const handleClearMask = () => {
dispatch(clearMask());
};
// Clear mask
useHotkeys(
'shift+c',
(e: KeyboardEvent) => {
e.preventDefault();
handleClearMask();
toast({
title: 'Mask Cleared',
status: 'success',
duration: 2500,
isClosable: true,
});
},
{
enabled: !isMaskEmpty,
},
[activeTabName, isMaskEmpty, isMaskEnabled]
);
return (
<IAIIconButton
aria-label="Clear Mask (Shift+C)"
tooltip="Clear Mask (Shift+C)"
icon={<FaPlus size={20} style={{ transform: 'rotate(45deg)' }} />}
onClick={handleClearMask}
isDisabled={isMaskEmpty || !isMaskEnabled}
/>
);
}

View File

@ -1,91 +0,0 @@
import React from 'react';
import { RgbaColor } from 'react-colorful';
import { FaPalette } from 'react-icons/fa';
import { useAppDispatch, useAppSelector } from 'app/store';
import IAIColorPicker from 'common/components/IAIColorPicker';
import IAIIconButton from 'common/components/IAIIconButton';
import IAIPopover from 'common/components/IAIPopover';
import { canvasSelector, setMaskColor } from 'features/canvas/canvasSlice';
import _ from 'lodash';
import { createSelector } from '@reduxjs/toolkit';
import { activeTabNameSelector } from 'features/options/optionsSelectors';
import { useHotkeys } from 'react-hotkeys-hook';
const maskColorPickerSelector = createSelector(
[canvasSelector, activeTabNameSelector],
(canvas, activeTabName) => {
const { isMaskEnabled, maskColor } = canvas;
return {
isMaskEnabled,
maskColor,
activeTabName,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
export default function IAICanvasMaskColorPicker() {
const { isMaskEnabled, maskColor, activeTabName } = useAppSelector(
maskColorPickerSelector
);
const dispatch = useAppDispatch();
const handleChangeMaskColor = (newColor: RgbaColor) => {
dispatch(setMaskColor(newColor));
};
// Hotkeys
// Decrease mask opacity
useHotkeys(
'shift+[',
(e: KeyboardEvent) => {
e.preventDefault();
handleChangeMaskColor({
...maskColor,
a: Math.max(maskColor.a - 0.05, 0),
});
},
{
enabled: true,
},
[activeTabName, isMaskEnabled, maskColor.a]
);
// Increase mask opacity
useHotkeys(
'shift+]',
(e: KeyboardEvent) => {
e.preventDefault();
handleChangeMaskColor({
...maskColor,
a: Math.min(maskColor.a + 0.05, 1),
});
},
{
enabled: true,
},
[activeTabName, isMaskEnabled, maskColor.a]
);
return (
<IAIPopover
trigger="hover"
styleClass="inpainting-color-picker"
triggerComponent={
<IAIIconButton
aria-label="Mask Color"
icon={<FaPalette />}
isDisabled={!isMaskEnabled}
cursor={'pointer'}
/>
}
>
<IAIColorPicker color={maskColor} onChange={handleChangeMaskColor} />
</IAIPopover>
);
}

View File

@ -1,69 +0,0 @@
import { createSelector } from '@reduxjs/toolkit';
import { MdInvertColors, MdInvertColorsOff } from 'react-icons/md';
import { useAppDispatch, useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import {
canvasSelector,
setShouldPreserveMaskedArea,
} from 'features/canvas/canvasSlice';
import _ from 'lodash';
import { activeTabNameSelector } from 'features/options/optionsSelectors';
import { useHotkeys } from 'react-hotkeys-hook';
const canvasMaskInvertSelector = createSelector(
[canvasSelector, activeTabNameSelector],
(canvas, activeTabName) => {
const { isMaskEnabled, shouldPreserveMaskedArea } = canvas;
return {
shouldPreserveMaskedArea,
isMaskEnabled,
activeTabName,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
export default function IAICanvasMaskInvertControl() {
const { shouldPreserveMaskedArea, isMaskEnabled, activeTabName } =
useAppSelector(canvasMaskInvertSelector);
const dispatch = useAppDispatch();
const handleToggleShouldInvertMask = () =>
dispatch(setShouldPreserveMaskedArea(!shouldPreserveMaskedArea));
// Invert mask
useHotkeys(
'shift+m',
(e: KeyboardEvent) => {
e.preventDefault();
handleToggleShouldInvertMask();
},
{
enabled: true,
},
[activeTabName, shouldPreserveMaskedArea, isMaskEnabled]
);
return (
<IAIIconButton
tooltip="Invert Mask Display (Shift+M)"
aria-label="Invert Mask Display (Shift+M)"
data-selected={shouldPreserveMaskedArea}
icon={
shouldPreserveMaskedArea ? (
<MdInvertColors size={22} />
) : (
<MdInvertColorsOff size={22} />
)
}
onClick={handleToggleShouldInvertMask}
isDisabled={!isMaskEnabled}
/>
);
}

View File

@ -1,56 +0,0 @@
import { useHotkeys } from 'react-hotkeys-hook';
import { BiHide, BiShow } from 'react-icons/bi';
import { createSelector } from 'reselect';
import { useAppDispatch, useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import { activeTabNameSelector } from 'features/options/optionsSelectors';
import { canvasSelector, setIsMaskEnabled } from 'features/canvas/canvasSlice';
import _ from 'lodash';
const canvasMaskVisibilitySelector = createSelector(
[canvasSelector, activeTabNameSelector],
(canvas, activeTabName) => {
const { isMaskEnabled } = canvas;
return { isMaskEnabled, activeTabName };
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
export default function IAICanvasMaskVisibilityControl() {
const dispatch = useAppDispatch();
const { isMaskEnabled, activeTabName } = useAppSelector(
canvasMaskVisibilitySelector
);
const handleToggleShouldShowMask = () =>
dispatch(setIsMaskEnabled(!isMaskEnabled));
// Hotkeys
// Show/hide mask
useHotkeys(
'h',
(e: KeyboardEvent) => {
e.preventDefault();
handleToggleShouldShowMask();
},
{
enabled: activeTabName === 'unifiedCanvas',
},
[activeTabName, isMaskEnabled]
);
return (
<IAIIconButton
aria-label="Hide Mask (H)"
tooltip="Hide Mask (H)"
data-alert={!isMaskEnabled}
icon={isMaskEnabled ? <BiShow size={22} /> : <BiHide size={22} />}
onClick={handleToggleShouldShowMask}
/>
);
}

View File

@ -1,45 +0,0 @@
import { FaVectorSquare } from 'react-icons/fa';
import { useAppDispatch, useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import {
canvasSelector,
setShouldShowBoundingBox,
} from 'features/canvas/canvasSlice';
import { createSelector } from '@reduxjs/toolkit';
import _ from 'lodash';
const canvasShowHideBoundingBoxControlSelector = createSelector(
canvasSelector,
(canvas) => {
const { shouldShowBoundingBox } = canvas;
return {
shouldShowBoundingBox,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
const IAICanvasShowHideBoundingBoxControl = () => {
const dispatch = useAppDispatch();
const { shouldShowBoundingBox } = useAppSelector(
canvasShowHideBoundingBoxControlSelector
);
return (
<IAIIconButton
aria-label="Hide Inpainting Box (Shift+H)"
tooltip="Hide Inpainting Box (Shift+H)"
icon={<FaVectorSquare />}
data-alert={!shouldShowBoundingBox}
onClick={() => {
dispatch(setShouldShowBoundingBox(!shouldShowBoundingBox));
}}
/>
);
};
export default IAICanvasShowHideBoundingBoxControl;

View File

@ -1,38 +0,0 @@
import { useHotkeys } from 'react-hotkeys-hook';
import { VscSplitHorizontal } from 'react-icons/vsc';
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import { setShowDualDisplay } from 'features/options/optionsSlice';
import { setDoesCanvasNeedScaling } from 'features/canvas/canvasSlice';
export default function IAICanvasSplitLayoutControl() {
const dispatch = useAppDispatch();
const showDualDisplay = useAppSelector(
(state: RootState) => state.options.showDualDisplay
);
const handleDualDisplay = () => {
dispatch(setShowDualDisplay(!showDualDisplay));
dispatch(setDoesCanvasNeedScaling(true));
};
// Hotkeys
// Toggle split view
useHotkeys(
'shift+j',
() => {
handleDualDisplay();
},
[showDualDisplay]
);
return (
<IAIIconButton
aria-label="Split Layout (Shift+J)"
tooltip="Split Layout (Shift+J)"
icon={<VscSplitHorizontal />}
data-selected={showDualDisplay}
onClick={handleDualDisplay}
/>
);
}

View File

@ -1,35 +1,30 @@
// lib
import { MutableRefObject, useCallback, useRef } from 'react';
import Konva from 'konva';
import { Layer, Stage } from 'react-konva';
import { Stage as StageType } from 'konva/lib/Stage';
// app
import { useAppDispatch, useAppSelector } from 'app/store';
import { useAppSelector } from 'app/store';
import {
initialCanvasImageSelector,
canvasSelector,
isStagingSelector,
shouldLockToInitialImageSelector,
} from 'features/canvas/canvasSlice';
// component
} from 'features/canvas/store/canvasSelectors';
import IAICanvasMaskLines from './IAICanvasMaskLines';
import IAICanvasBrushPreview from './IAICanvasBrushPreview';
import { Vector2d } from 'konva/lib/types';
import IAICanvasBoundingBox from './IAICanvasBoundingBox';
import useCanvasHotkeys from './hooks/useCanvasHotkeys';
import IAICanvasBoundingBox from './IAICanvasToolbar/IAICanvasBoundingBox';
import useCanvasHotkeys from '../hooks/useCanvasHotkeys';
import _ from 'lodash';
import { createSelector } from '@reduxjs/toolkit';
import { activeTabNameSelector } from 'features/options/optionsSelectors';
import IAICanvasMaskCompositer from './IAICanvasMaskCompositer';
import useCanvasWheel from './hooks/useCanvasZoom';
import useCanvasMouseDown from './hooks/useCanvasMouseDown';
import useCanvasMouseUp from './hooks/useCanvasMouseUp';
import useCanvasMouseMove from './hooks/useCanvasMouseMove';
import useCanvasMouseEnter from './hooks/useCanvasMouseEnter';
import useCanvasMouseOut from './hooks/useCanvasMouseOut';
import useCanvasDragMove from './hooks/useCanvasDragMove';
import useCanvasWheel from '../hooks/useCanvasZoom';
import useCanvasMouseDown from '../hooks/useCanvasMouseDown';
import useCanvasMouseUp from '../hooks/useCanvasMouseUp';
import useCanvasMouseMove from '../hooks/useCanvasMouseMove';
import useCanvasMouseEnter from '../hooks/useCanvasMouseEnter';
import useCanvasMouseOut from '../hooks/useCanvasMouseOut';
import useCanvasDragMove from '../hooks/useCanvasDragMove';
import IAICanvasObjectRenderer from './IAICanvasObjectRenderer';
import IAICanvasGrid from './IAICanvasGrid';
import IAICanvasIntermediateImage from './IAICanvasIntermediateImage';

View File

@ -3,8 +3,8 @@ import { GroupConfig } from 'konva/lib/Group';
import _ from 'lodash';
import { Circle, Group } from 'react-konva';
import { useAppSelector } from 'app/store';
import { canvasSelector } from 'features/canvas/canvasSlice';
import { rgbaColorToString } from './util/colorToString';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { rgbaColorToString } from 'features/canvas/util/colorToString';
const canvasBrushPreviewSelector = createSelector(
canvasSelector,

View File

@ -6,7 +6,7 @@ import { useAppSelector } from 'app/store';
import _ from 'lodash';
import { ReactNode, useCallback, useLayoutEffect, useState } from 'react';
import { Group, Line as KonvaLine } from 'react-konva';
import { canvasSelector } from './canvasSlice';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
const selector = createSelector(
[canvasSelector],

View File

@ -2,9 +2,9 @@ import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store';
import { RectConfig } from 'konva/lib/shapes/Rect';
import { Rect } from 'react-konva';
import { canvasSelector } from './canvasSlice';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { rgbaColorToString } from './util/colorToString';
import { rgbaColorToString } from 'features/canvas/util/colorToString';
import { useCallback, useEffect, useRef, useState } from 'react';
import Konva from 'konva';

View File

@ -2,7 +2,8 @@ import { GroupConfig } from 'konva/lib/Group';
import { Group, Line } from 'react-konva';
import { useAppSelector } from 'app/store';
import { createSelector } from '@reduxjs/toolkit';
import { canvasSelector, isCanvasMaskLine } from './canvasSlice';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { isCanvasMaskLine } from '../store/canvasTypes';
import _ from 'lodash';
export const canvasLinesSelector = createSelector(

View File

@ -2,13 +2,10 @@ import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store';
import _ from 'lodash';
import { Group, Line } from 'react-konva';
import {
canvasSelector,
isCanvasBaseImage,
isCanvasBaseLine,
} from './canvasSlice';
import { isCanvasBaseImage, isCanvasBaseLine } from '../store/canvasTypes';
import IAICanvasImage from './IAICanvasImage';
import { rgbaColorToString } from './util/colorToString';
import { rgbaColorToString } from 'features/canvas/util/colorToString';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
const selector = createSelector(
[canvasSelector],

View File

@ -3,14 +3,13 @@ import { useLayoutEffect, useRef } from 'react';
import { useAppDispatch, useAppSelector } from 'app/store';
import { activeTabNameSelector } from 'features/options/optionsSelectors';
import {
initialCanvasImageSelector,
canvasSelector,
resizeAndScaleCanvas,
resizeCanvas,
setCanvasContainerDimensions,
setDoesCanvasNeedScaling,
} from 'features/canvas/canvasSlice';
} from 'features/canvas/store/canvasSlice';
import { createSelector } from '@reduxjs/toolkit';
import { canvasSelector, initialCanvasImageSelector } from 'features/canvas/store/canvasSelectors';
const canvasResizerSelector = createSelector(
canvasSelector,

View File

@ -1,10 +1,10 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store';
import { useAppSelector } from 'app/store';
import { GroupConfig } from 'konva/lib/Group';
import _ from 'lodash';
import { useCallback, useState } from 'react';
import { useState } from 'react';
import { Group, Rect } from 'react-konva';
import { canvasSelector } from './canvasSlice';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import IAICanvasImage from './IAICanvasImage';
const selector = createSelector(

View File

@ -21,13 +21,13 @@ import {
FaEyeSlash,
FaTrash,
} from 'react-icons/fa';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import {
commitStagingAreaImage,
canvasSelector,
discardStagedImages,
nextStagingAreaImage,
prevStagingAreaImage,
} from './canvasSlice';
} from 'features/canvas/store/canvasSlice';
const selector = createSelector(
[canvasSelector],

View File

@ -1,7 +1,7 @@
import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store';
import _ from 'lodash';
import { canvasSelector } from './canvasSlice';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
const roundToHundreth = (val: number): number => {
return Math.round(val * 100) / 100;

View File

@ -11,13 +11,15 @@ import { roundToMultiple } from 'common/util/roundDownToMultiple';
import {
initialCanvasImageSelector,
canvasSelector,
shouldLockToInitialImageSelector,
} from 'features/canvas/store/canvasSelectors';
import {
setBoundingBoxCoordinates,
setBoundingBoxDimensions,
setIsMouseOverBoundingBox,
setIsMovingBoundingBox,
setIsTransformingBoundingBox,
shouldLockToInitialImageSelector,
} from 'features/canvas/canvasSlice';
} from 'features/canvas/store/canvasSlice';
import { GroupConfig } from 'konva/lib/Group';
import { activeTabNameSelector } from 'features/options/optionsSelectors';

View File

@ -1,11 +1,5 @@
import { createSelector } from '@reduxjs/toolkit';
import {
canvasSelector,
isStagingSelector,
setBrushColor,
setBrushSize,
setTool,
} from './canvasSlice';
import { setBrushColor, setBrushSize, setTool } from 'features/canvas/store/canvasSlice';
import { useAppDispatch, useAppSelector } from 'app/store';
import _ from 'lodash';
import IAIIconButton from 'common/components/IAIIconButton';
@ -14,6 +8,7 @@ import IAIPopover from 'common/components/IAIPopover';
import IAIColorPicker from 'common/components/IAIColorPicker';
import IAISlider from 'common/components/IAISlider';
import { Flex } from '@chakra-ui/react';
import { canvasSelector, isStagingSelector } from 'features/canvas/store/canvasSelectors';
export const selector = createSelector(
[canvasSelector, isStagingSelector],

View File

@ -1,10 +1,8 @@
import { createSelector } from '@reduxjs/toolkit';
import {
canvasSelector,
isStagingSelector,
setEraserSize,
setTool,
} from './canvasSlice';
} from 'features/canvas/store/canvasSlice';
import { useAppDispatch, useAppSelector } from 'app/store';
import _ from 'lodash';
import IAIIconButton from 'common/components/IAIIconButton';
@ -13,6 +11,7 @@ import IAIPopover from 'common/components/IAIPopover';
import IAISlider from 'common/components/IAISlider';
import { Flex } from '@chakra-ui/react';
import { useHotkeys } from 'react-hotkeys-hook';
import { canvasSelector, isStagingSelector } from 'features/canvas/store/canvasSelectors';
export const selector = createSelector(
[canvasSelector, isStagingSelector],

View File

@ -2,12 +2,11 @@ import { Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import {
clearMask,
canvasSelector,
setIsMaskEnabled,
setLayer,
setMaskColor,
setShouldPreserveMaskedArea,
} from './canvasSlice';
} from 'features/canvas/store/canvasSlice';
import { useAppDispatch, useAppSelector } from 'app/store';
import _ from 'lodash';
import IAIIconButton from 'common/components/IAIIconButton';
@ -16,6 +15,7 @@ import IAIPopover from 'common/components/IAIPopover';
import IAICheckbox from 'common/components/IAICheckbox';
import IAIColorPicker from 'common/components/IAIColorPicker';
import IAIButton from 'common/components/IAIButton';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
export const selector = createSelector(
[canvasSelector],

View File

@ -4,9 +4,10 @@ import { FaRedo } from 'react-icons/fa';
import { useAppDispatch, useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import { activeTabNameSelector } from 'features/options/optionsSelectors';
import { canvasSelector, redo } from 'features/canvas/canvasSlice';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import _ from 'lodash';
import { redo } from 'features/canvas/store/canvasSlice';
const canvasRedoSelector = createSelector(
[canvasSelector, activeTabNameSelector],

View File

@ -1,19 +1,19 @@
import { Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import {
canvasSelector,
setShouldAutoSave,
setShouldDarkenOutsideBoundingBox,
setShouldShowGrid,
setShouldShowIntermediates,
setShouldSnapToGrid,
} from './canvasSlice';
} from 'features/canvas/store/canvasSlice';
import { useAppDispatch, useAppSelector } from 'app/store';
import _ from 'lodash';
import IAIIconButton from 'common/components/IAIIconButton';
import { FaWrench } from 'react-icons/fa';
import IAIPopover from 'common/components/IAIPopover';
import IAICheckbox from 'common/components/IAICheckbox';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
export const canvasControlsSelector = createSelector(
[canvasSelector],

View File

@ -2,16 +2,14 @@ import { ButtonGroup } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import {
resizeAndScaleCanvas,
isStagingSelector,
resetCanvas,
resetCanvasView,
setShouldLockToInitialImage,
setTool,
canvasSelector,
} from './canvasSlice';
} from 'features/canvas/store/canvasSlice';
import { useAppDispatch, useAppSelector } from 'app/store';
import _ from 'lodash';
import { canvasImageLayerRef, stageRef } from './IAICanvas';
import { canvasImageLayerRef, stageRef } from '../IAICanvas';
import IAIIconButton from 'common/components/IAIIconButton';
import {
FaArrowsAlt,
@ -23,15 +21,16 @@ import {
FaTrash,
FaUpload,
} from 'react-icons/fa';
import IAICanvasUndoButton from './IAICanvasControls/IAICanvasUndoButton';
import IAICanvasRedoButton from './IAICanvasControls/IAICanvasRedoButton';
import IAICanvasUndoButton from './IAICanvasUndoButton';
import IAICanvasRedoButton from './IAICanvasRedoButton';
import IAICanvasSettingsButtonPopover from './IAICanvasSettingsButtonPopover';
import IAICanvasEraserButtonPopover from './IAICanvasEraserButtonPopover';
import IAICanvasBrushButtonPopover from './IAICanvasBrushButtonPopover';
import IAICanvasMaskButtonPopover from './IAICanvasMaskButtonPopover';
import { mergeAndUploadCanvas } from './util/mergeAndUploadCanvas';
import { mergeAndUploadCanvas } from 'features/canvas/util/mergeAndUploadCanvas';
import IAICheckbox from 'common/components/IAICheckbox';
import { ChangeEvent } from 'react';
import { canvasSelector, isStagingSelector } from 'features/canvas/store/canvasSelectors';
export const selector = createSelector(
[canvasSelector, isStagingSelector],

View File

@ -3,10 +3,11 @@ import { useHotkeys } from 'react-hotkeys-hook';
import { FaUndo } from 'react-icons/fa';
import { useAppDispatch, useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import { canvasSelector, undo } from 'features/canvas/canvasSlice';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import _ from 'lodash';
import { activeTabNameSelector } from 'features/options/optionsSelectors';
import { undo } from 'features/canvas/store/canvasSlice';
const canvasUndoSelector = createSelector(
[canvasSelector, activeTabNameSelector],

View File

@ -3,12 +3,11 @@ import { useAppDispatch, useAppSelector } from 'app/store';
import { KonvaEventObject } from 'konva/lib/Node';
import _ from 'lodash';
import { useCallback } from 'react';
import { canvasSelector, isStagingSelector } from 'features/canvas/store/canvasSelectors';
import {
canvasSelector,
isStagingSelector,
setIsMovingStage,
setStageCoordinates,
} from '../canvasSlice';
} from 'features/canvas/store/canvasSlice';
const selector = createSelector(
[canvasSelector, isStagingSelector],

View File

@ -3,15 +3,15 @@ import _ from 'lodash';
import { useHotkeys } from 'react-hotkeys-hook';
import { activeTabNameSelector } from 'features/options/optionsSelectors';
import {
CanvasTool,
setShouldShowBoundingBox,
setTool,
toggleShouldLockBoundingBox,
} from 'features/canvas/canvasSlice';
} from 'features/canvas/store/canvasSlice';
import { useAppDispatch, useAppSelector } from 'app/store';
import { canvasSelector } from '../canvasSlice';
import { useRef } from 'react';
import { stageRef } from '../IAICanvas';
import { stageRef } from '../components/IAICanvas';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { CanvasTool } from '../store/canvasTypes';
const selector = createSelector(
[canvasSelector, activeTabNameSelector],

View File

@ -5,13 +5,12 @@ import Konva from 'konva';
import { KonvaEventObject } from 'konva/lib/Node';
import _ from 'lodash';
import { MutableRefObject, useCallback } from 'react';
import { canvasSelector, isStagingSelector } from 'features/canvas/store/canvasSelectors';
import {
addLine,
canvasSelector,
isStagingSelector,
setIsDrawing,
setIsMovingStage,
} from '../canvasSlice';
} from 'features/canvas/store/canvasSlice';
import getScaledCursorPosition from '../util/getScaledCursorPosition';
const selector = createSelector(

View File

@ -5,12 +5,11 @@ import Konva from 'konva';
import { KonvaEventObject } from 'konva/lib/Node';
import _ from 'lodash';
import { MutableRefObject, useCallback } from 'react';
import { canvasSelector, isStagingSelector } from 'features/canvas/store/canvasSelectors';
import {
addLine,
canvasSelector,
isStagingSelector,
setIsDrawing,
} from '../canvasSlice';
} from 'features/canvas/store/canvasSlice';
import getScaledCursorPosition from '../util/getScaledCursorPosition';
const selector = createSelector(

View File

@ -5,12 +5,11 @@ import Konva from 'konva';
import { Vector2d } from 'konva/lib/types';
import _ from 'lodash';
import { MutableRefObject, useCallback } from 'react';
import { canvasSelector, isStagingSelector } from 'features/canvas/store/canvasSelectors';
import {
addPointToCurrentLine,
canvasSelector,
isStagingSelector,
setCursorPosition,
} from '../canvasSlice';
} from 'features/canvas/store/canvasSlice';
import getScaledCursorPosition from '../util/getScaledCursorPosition';
const selector = createSelector(

View File

@ -1,6 +1,6 @@
import { useAppDispatch } from 'app/store';
import { useCallback } from 'react';
import { setCursorPosition, setIsDrawing } from '../canvasSlice';
import { setCursorPosition, setIsDrawing } from 'features/canvas/store/canvasSlice';
const useCanvasMouseOut = () => {
const dispatch = useAppDispatch();

View File

@ -4,14 +4,13 @@ import { activeTabNameSelector } from 'features/options/optionsSelectors';
import Konva from 'konva';
import _ from 'lodash';
import { MutableRefObject, useCallback } from 'react';
import { canvasSelector, isStagingSelector } from 'features/canvas/store/canvasSelectors';
import {
// addPointToCurrentEraserLine,
addPointToCurrentLine,
canvasSelector,
isStagingSelector,
setIsDrawing,
setIsMovingStage,
} from '../canvasSlice';
} from 'features/canvas/store/canvasSlice';
import getScaledCursorPosition from '../util/getScaledCursorPosition';
const selector = createSelector(

View File

@ -6,12 +6,11 @@ import { KonvaEventObject } from 'konva/lib/Node';
import _ from 'lodash';
import { MutableRefObject, useCallback } from 'react';
import {
initialCanvasImageSelector,
canvasSelector,
setStageCoordinates,
setStageScale,
initialCanvasImageSelector,
shouldLockToInitialImageSelector,
} from '../canvasSlice';
} from 'features/canvas/store/canvasSelectors';
import { setStageCoordinates, setStageScale } from 'features/canvas/store/canvasSlice';
import {
CANVAS_SCALE_BY,
MAX_CANVAS_SCALE,

View File

@ -1,10 +1,14 @@
import * as InvokeAI from 'app/invokeai';
import { CanvasState, initialLayerState } from './canvasSlice';
import { initialLayerState } from './canvasSlice';
import { CanvasState } from './canvasTypes';
import {
roundDownToMultiple,
roundToMultiple,
} from 'common/util/roundDownToMultiple';
import _ from 'lodash';
import { mergeAndUploadCanvas } from '../util/mergeAndUploadCanvas';
import { uploadImage } from 'features/gallery/util/uploadImage';
import { ActionReducerMapBuilder } from '@reduxjs/toolkit';
export const setInitialCanvasImage_reducer = (
state: CanvasState,
@ -50,3 +54,39 @@ export const setInitialCanvasImage_reducer = (
state.isCanvasInitialized = false;
state.doesCanvasNeedScaling = true;
};
export const canvasExtraReducers = (
builder: ActionReducerMapBuilder<CanvasState>
) => {
builder.addCase(mergeAndUploadCanvas.fulfilled, (state, action) => {
if (!action.payload) return;
const { image, kind, originalBoundingBox } = action.payload;
if (kind === 'temp_merged_canvas') {
state.pastLayerStates.push({
...state.layerState,
});
state.futureLayerStates = [];
state.layerState.objects = [
{
kind: 'image',
layer: 'base',
...originalBoundingBox,
image,
},
];
}
});
builder.addCase(uploadImage.fulfilled, (state, action) => {
if (!action.payload) return;
const { image, kind, activeTabName } = action.payload;
if (kind !== 'init') return;
if (activeTabName === 'unifiedCanvas') {
setInitialCanvasImage_reducer(state, image);
}
});
};

View File

@ -0,0 +1,15 @@
import { RootState } from 'app/store';
import { CanvasImage, CanvasState, isCanvasBaseImage } from './canvasTypes';
export const canvasSelector = (state: RootState): CanvasState => state.canvas;
export const isStagingSelector = (state: RootState): boolean =>
state.canvas.layerState.stagingArea.images.length > 0;
export const shouldLockToInitialImageSelector = (state: RootState): boolean =>
state.canvas.shouldLockToInitialImage;
export const initialCanvasImageSelector = (
state: RootState
): CanvasImage | undefined =>
state.canvas.layerState.objects.find(isCanvasBaseImage);

View File

@ -5,125 +5,23 @@ import { RgbaColor } from 'react-colorful';
import * as InvokeAI from 'app/invokeai';
import _ from 'lodash';
import { roundDownToMultiple } from 'common/util/roundDownToMultiple';
import { RootState } from 'app/store';
import { mergeAndUploadCanvas } from './util/mergeAndUploadCanvas';
import { uploadImage } from 'features/gallery/util/uploadImage';
import { setInitialCanvasImage_reducer } from './canvasReducers';
import calculateScale from './util/calculateScale';
import calculateCoordinates from './util/calculateCoordinates';
import floorCoordinates from './util/floorCoordinates';
export type CanvasLayer = 'base' | 'mask';
export type CanvasDrawingTool = 'brush' | 'eraser';
export type CanvasTool = CanvasDrawingTool | 'move';
export type Dimensions = {
width: number;
height: number;
};
export type CanvasAnyLine = {
kind: 'line';
tool: CanvasDrawingTool;
strokeWidth: number;
points: number[];
};
export type CanvasImage = {
kind: 'image';
layer: 'base';
x: number;
y: number;
width: number;
height: number;
image: InvokeAI.Image;
};
export type CanvasMaskLine = CanvasAnyLine & {
layer: 'mask';
};
export type CanvasLine = CanvasAnyLine & {
layer: 'base';
color?: RgbaColor;
};
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 =>
obj.kind === 'line' && obj.layer === 'mask';
export const isCanvasBaseLine = (obj: CanvasObject): obj is CanvasLine =>
obj.kind === 'line' && obj.layer === 'base';
export const isCanvasBaseImage = (obj: CanvasObject): obj is CanvasImage =>
obj.kind === 'image' && obj.layer === 'base';
export const isCanvasAnyLine = (
obj: CanvasObject
): obj is CanvasMaskLine | CanvasLine => obj.kind === 'line';
export interface CanvasState {
boundingBoxCoordinates: Vector2d;
boundingBoxDimensions: Dimensions;
boundingBoxPreviewFill: RgbaColor;
brushColor: RgbaColor;
brushSize: number;
canvasContainerDimensions: Dimensions;
cursorPosition: Vector2d | null;
doesCanvasNeedScaling: boolean;
eraserSize: number;
futureLayerStates: CanvasLayerState[];
inpaintReplace: number;
intermediateImage?: InvokeAI.Image;
isCanvasInitialized: boolean;
isDrawing: boolean;
isMaskEnabled: boolean;
isMouseOverBoundingBox: boolean;
isMoveBoundingBoxKeyHeld: boolean;
isMoveStageKeyHeld: boolean;
isMovingBoundingBox: boolean;
isMovingStage: boolean;
isTransformingBoundingBox: boolean;
layer: CanvasLayer;
layerState: CanvasLayerState;
maskColor: RgbaColor;
maxHistory: number;
minimumStageScale: number;
pastLayerStates: CanvasLayerState[];
shouldAutoSave: boolean;
shouldDarkenOutsideBoundingBox: boolean;
shouldLockBoundingBox: boolean;
shouldLockToInitialImage: boolean;
shouldPreserveMaskedArea: boolean;
shouldShowBoundingBox: boolean;
shouldShowBrush: boolean;
shouldShowBrushPreview: boolean;
shouldShowCheckboardTransparency: boolean;
shouldShowGrid: boolean;
shouldShowIntermediates: boolean;
shouldSnapToGrid: boolean;
shouldUseInpaintReplace: boolean;
stageCoordinates: Vector2d;
stageDimensions: Dimensions;
stageScale: number;
tool: CanvasTool;
}
import {
canvasExtraReducers,
setInitialCanvasImage_reducer,
} from './canvasExtraReducers';
import calculateScale from '../util/calculateScale';
import calculateCoordinates from '../util/calculateCoordinates';
import floorCoordinates from '../util/floorCoordinates';
import {
CanvasLayer,
CanvasLayerState,
CanvasState,
CanvasTool,
Dimensions,
isCanvasAnyLine,
isCanvasBaseImage,
isCanvasMaskLine,
} from './canvasTypes';
export const initialLayerState: CanvasLayerState = {
objects: [],
@ -473,9 +371,6 @@ export const canvasSlice = createSlice({
const { width: imageWidth, height: imageHeight } = initialCanvasImage;
// const { clientWidth, clientHeight, imageWidth, imageHeight } =
// action.payload;
const { shouldLockToInitialImage } = state;
const padding = shouldLockToInitialImage ? 1 : 0.95;
@ -604,40 +499,7 @@ export const canvasSlice = createSlice({
state.shouldLockToInitialImage = action.payload;
},
},
extraReducers: (builder) => {
builder.addCase(mergeAndUploadCanvas.fulfilled, (state, action) => {
if (!action.payload) return;
const { image, kind, originalBoundingBox } = action.payload;
if (kind === 'temp_merged_canvas') {
state.pastLayerStates.push({
...state.layerState,
});
state.futureLayerStates = [];
state.layerState.objects = [
{
kind: 'image',
layer: 'base',
...originalBoundingBox,
image,
},
];
}
});
builder.addCase(uploadImage.fulfilled, (state, action) => {
if (!action.payload) return;
const { image, kind, activeTabName } = action.payload;
if (kind !== 'init') return;
if (activeTabName === 'unifiedCanvas') {
setInitialCanvasImage_reducer(state, image);
}
});
},
extraReducers: canvasExtraReducers,
});
export const {
@ -699,16 +561,3 @@ export const {
} = canvasSlice.actions;
export default canvasSlice.reducer;
export const canvasSelector = (state: RootState): CanvasState => state.canvas;
export const isStagingSelector = (state: RootState): boolean =>
state.canvas.layerState.stagingArea.images.length > 0;
export const shouldLockToInitialImageSelector = (state: RootState): boolean =>
state.canvas.shouldLockToInitialImage;
export const initialCanvasImageSelector = (
state: RootState
): CanvasImage | undefined =>
state.canvas.layerState.objects.find(isCanvasBaseImage);

View File

@ -0,0 +1,115 @@
import * as InvokeAI from 'app/invokeai';
import { Vector2d } from 'konva/lib/types';
import { RgbaColor } from 'react-colorful';
export type CanvasLayer = 'base' | 'mask';
export type CanvasDrawingTool = 'brush' | 'eraser';
export type CanvasTool = CanvasDrawingTool | 'move';
export type Dimensions = {
width: number;
height: number;
};
export type CanvasAnyLine = {
kind: 'line';
tool: CanvasDrawingTool;
strokeWidth: number;
points: number[];
};
export type CanvasImage = {
kind: 'image';
layer: 'base';
x: number;
y: number;
width: number;
height: number;
image: InvokeAI.Image;
};
export type CanvasMaskLine = CanvasAnyLine & {
layer: 'mask';
};
export type CanvasLine = CanvasAnyLine & {
layer: 'base';
color?: RgbaColor;
};
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 =>
obj.kind === 'line' && obj.layer === 'mask';
export const isCanvasBaseLine = (obj: CanvasObject): obj is CanvasLine =>
obj.kind === 'line' && obj.layer === 'base';
export const isCanvasBaseImage = (obj: CanvasObject): obj is CanvasImage =>
obj.kind === 'image' && obj.layer === 'base';
export const isCanvasAnyLine = (
obj: CanvasObject
): obj is CanvasMaskLine | CanvasLine => obj.kind === 'line';
export interface CanvasState {
boundingBoxCoordinates: Vector2d;
boundingBoxDimensions: Dimensions;
boundingBoxPreviewFill: RgbaColor;
brushColor: RgbaColor;
brushSize: number;
canvasContainerDimensions: Dimensions;
cursorPosition: Vector2d | null;
doesCanvasNeedScaling: boolean;
eraserSize: number;
futureLayerStates: CanvasLayerState[];
inpaintReplace: number;
intermediateImage?: InvokeAI.Image;
isCanvasInitialized: boolean;
isDrawing: boolean;
isMaskEnabled: boolean;
isMouseOverBoundingBox: boolean;
isMoveBoundingBoxKeyHeld: boolean;
isMoveStageKeyHeld: boolean;
isMovingBoundingBox: boolean;
isMovingStage: boolean;
isTransformingBoundingBox: boolean;
layer: CanvasLayer;
layerState: CanvasLayerState;
maskColor: RgbaColor;
maxHistory: number;
minimumStageScale: number;
pastLayerStates: CanvasLayerState[];
shouldAutoSave: boolean;
shouldDarkenOutsideBoundingBox: boolean;
shouldLockBoundingBox: boolean;
shouldLockToInitialImage: boolean;
shouldPreserveMaskedArea: boolean;
shouldShowBoundingBox: boolean;
shouldShowBrush: boolean;
shouldShowBrushPreview: boolean;
shouldShowCheckboardTransparency: boolean;
shouldShowGrid: boolean;
shouldShowIntermediates: boolean;
shouldSnapToGrid: boolean;
shouldUseInpaintReplace: boolean;
stageCoordinates: Vector2d;
stageDimensions: Dimensions;
stageScale: number;
tool: CanvasTool;
}

View File

@ -1,6 +1,6 @@
import Konva from 'konva';
import { IRect } from 'konva/lib/types';
import { CanvasMaskLine } from 'features/canvas/canvasSlice';
import { CanvasMaskLine } from 'features/canvas/store/canvasTypes';
/**
* Generating a mask image from InpaintingCanvas.tsx is not as simple

View File

@ -39,7 +39,7 @@ import {
setDoesCanvasNeedScaling,
setInitialCanvasImage,
setShouldLockToInitialImage,
} from 'features/canvas/canvasSlice';
} from 'features/canvas/store/canvasSlice';
import { GalleryState } from './gallerySlice';
import { activeTabNameSelector } from 'features/options/optionsSelectors';
import IAIPopover from 'common/components/IAIPopover';

View File

@ -26,7 +26,7 @@ import {
setDoesCanvasNeedScaling,
setInitialCanvasImage,
setShouldLockToInitialImage,
} from 'features/canvas/canvasSlice';
} from 'features/canvas/store/canvasSlice';
import { hoverableImageSelector } from './gallerySliceSelectors';
interface HoverableImageProps {

View File

@ -31,7 +31,7 @@ import IAIPopover from 'common/components/IAIPopover';
import IAISlider from 'common/components/IAISlider';
import { BiReset } from 'react-icons/bi';
import IAICheckbox from 'common/components/IAICheckbox';
import { setDoesCanvasNeedScaling } from 'features/canvas/canvasSlice';
import { setDoesCanvasNeedScaling } from 'features/canvas/store/canvasSlice';
import _ from 'lodash';
import useClickOutsideWatcher from 'common/hooks/useClickOutsideWatcher';

View File

@ -1,11 +1,9 @@
import React from 'react';
import { useAppDispatch, useAppSelector } from 'app/store';
import IAICheckbox from 'common/components/IAICheckbox';
import {
canvasSelector,
setShouldDarkenOutsideBoundingBox,
} from 'features/canvas/canvasSlice';
import { setShouldDarkenOutsideBoundingBox } from 'features/canvas/store/canvasSlice';
import { createSelector } from '@reduxjs/toolkit';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
const selector = createSelector(
canvasSelector,

View File

@ -3,15 +3,13 @@ import IAISlider from 'common/components/IAISlider';
import { useAppDispatch, useAppSelector } from 'app/store';
import { createSelector } from '@reduxjs/toolkit';
import {
canvasSelector,
setBoundingBoxDimensions,
} from 'features/canvas/canvasSlice';
import { setBoundingBoxDimensions } from 'features/canvas/store/canvasSlice';
import { roundDownToMultiple } from 'common/util/roundDownToMultiple';
import _ from 'lodash';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
const boundingBoxDimensionsSelector = createSelector(
const selector = createSelector(
canvasSelector,
(canvas) => {
const { stageDimensions, boundingBoxDimensions, shouldLockBoundingBox } =
@ -40,7 +38,7 @@ export default function BoundingBoxDimensionSlider(
const { dimension, label } = props;
const dispatch = useAppDispatch();
const { shouldLockBoundingBox, stageDimensions, boundingBoxDimensions } =
useAppSelector(boundingBoxDimensionsSelector);
useAppSelector(selector);
const canvasDimension = stageDimensions[dimension];
const boundingBoxDimension = boundingBoxDimensions[dimension];

View File

@ -1,19 +1,17 @@
import React from 'react';
import { useAppDispatch, useAppSelector } from 'app/store';
import IAICheckbox from 'common/components/IAICheckbox';
import {
canvasSelector,
setShouldLockBoundingBox,
} from 'features/canvas/canvasSlice';
import { setShouldLockBoundingBox } from 'features/canvas/store/canvasSlice';
import { createSelector } from '@reduxjs/toolkit';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
const boundingBoxLockSelector = createSelector(
const selector = createSelector(
canvasSelector,
(canvas) => canvas.shouldLockBoundingBox
);
export default function BoundingBoxLock() {
const shouldLockBoundingBox = useAppSelector(boundingBoxLockSelector);
const shouldLockBoundingBox = useAppSelector(selector);
const dispatch = useAppDispatch();
const handleChangeShouldLockBoundingBox = () => {

View File

@ -2,19 +2,17 @@ import React from 'react';
import { BiHide, BiShow } from 'react-icons/bi';
import { useAppDispatch, useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import {
canvasSelector,
setShouldShowBoundingBox,
} from 'features/canvas/canvasSlice';
import { setShouldShowBoundingBox } from 'features/canvas/store/canvasSlice';
import { createSelector } from '@reduxjs/toolkit';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
const boundingBoxVisibilitySelector = createSelector(
const selector = createSelector(
canvasSelector,
(canvas) => canvas.shouldShowBoundingBox
);
export default function BoundingBoxVisibility() {
const shouldShowBoundingBox = useAppSelector(boundingBoxVisibilitySelector);
const shouldShowBoundingBox = useAppSelector(selector);
const dispatch = useAppDispatch();
const handleShowBoundingBox = () =>

View File

@ -2,13 +2,11 @@ import { useToast } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store';
import IAIButton from 'common/components/IAIButton';
import {
canvasSelector,
setClearBrushHistory,
} from 'features/canvas/canvasSlice';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import { setClearBrushHistory } from 'features/canvas/store/canvasSlice';
import _ from 'lodash';
const clearBrushHistorySelector = createSelector(
const selector = createSelector(
canvasSelector,
(canvas) => {
const { pastLayerStates, futureLayerStates } = canvas;
@ -30,7 +28,7 @@ export default function ClearBrushHistory() {
const dispatch = useAppDispatch();
const toast = useToast();
const { mayClearBrushHistory } = useAppSelector(clearBrushHistorySelector);
const { mayClearBrushHistory } = useAppSelector(selector);
const handleClearBrushHistory = () => {
dispatch(setClearBrushHistory());

View File

@ -6,12 +6,12 @@ import IAISwitch from '../../../../common/components/IAISwitch';
import IAISlider from '../../../../common/components/IAISlider';
import { Flex } from '@chakra-ui/react';
import {
canvasSelector,
setInpaintReplace,
setShouldUseInpaintReplace,
} from 'features/canvas/canvasSlice';
} from 'features/canvas/store/canvasSlice';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
const canvasInpaintReplaceSelector = createSelector(
const selector = createSelector(
canvasSelector,
(canvas) => {
const { inpaintReplace, shouldUseInpaintReplace } = canvas;
@ -28,9 +28,7 @@ const canvasInpaintReplaceSelector = createSelector(
);
export default function InpaintReplace() {
const { inpaintReplace, shouldUseInpaintReplace } = useAppSelector(
canvasInpaintReplaceSelector
);
const { inpaintReplace, shouldUseInpaintReplace } = useAppSelector(selector);
const dispatch = useAppDispatch();

View File

@ -2,7 +2,7 @@ import { MdPhotoLibrary } from 'react-icons/md';
import { RootState, useAppDispatch, useAppSelector } from 'app/store';
import IAIIconButton from 'common/components/IAIIconButton';
import { setShouldShowGallery } from 'features/gallery/gallerySlice';
import { setDoesCanvasNeedScaling } from 'features/canvas/canvasSlice';
import { setDoesCanvasNeedScaling } from 'features/canvas/store/canvasSlice';
const FloatingGalleryButton = () => {
const dispatch = useAppDispatch();

View File

@ -10,7 +10,7 @@ import CancelButton from 'features/options/ProcessButtons/CancelButton';
import InvokeButton from 'features/options/ProcessButtons/InvokeButton';
import _ from 'lodash';
import LoopbackButton from 'features/options/ProcessButtons/Loopback';
import { setDoesCanvasNeedScaling } from 'features/canvas/canvasSlice';
import { setDoesCanvasNeedScaling } from 'features/canvas/store/canvasSlice';
const canInvokeSelector = createSelector(
(state: RootState) => state.options,

View File

@ -14,7 +14,7 @@ import {
setShouldPinOptionsPanel,
setShouldShowOptionsPanel,
} from 'features/options/optionsSlice';
import { setDoesCanvasNeedScaling } from 'features/canvas/canvasSlice';
import { setDoesCanvasNeedScaling } from 'features/canvas/store/canvasSlice';
import InvokeAILogo from 'assets/images/logo.png';
type Props = { children: ReactNode };

View File

@ -19,7 +19,7 @@ import {
import ImageToImageWorkarea from './ImageToImage';
import TextToImageWorkarea from './TextToImage';
import Lightbox from 'features/lightbox/Lightbox';
import { setDoesCanvasNeedScaling } from 'features/canvas/canvasSlice';
import { setDoesCanvasNeedScaling } from 'features/canvas/store/canvasSlice';
import UnifiedCanvasWorkarea from './UnifiedCanvas/UnifiedCanvasWorkarea';
import { setShouldShowGallery } from 'features/gallery/gallerySlice';
import UnifiedCanvasIcon from 'common/icons/UnifiedCanvasIcon';

View File

@ -10,7 +10,7 @@ import {
OptionsState,
setShowDualDisplay,
} from 'features/options/optionsSlice';
import { setDoesCanvasNeedScaling } from 'features/canvas/canvasSlice';
import { setDoesCanvasNeedScaling } from 'features/canvas/store/canvasSlice';
const workareaSelector = createSelector(
[(state: RootState) => state.options, activeTabNameSelector],

View File

@ -1,4 +1,4 @@
@use '../../styles/Mixins/' as *;
@use '../../../styles/Mixins/' as *;
.inpainting-main-area {
display: flex;

View File

@ -1,16 +1,13 @@
import { createSelector } from '@reduxjs/toolkit';
// import IAICanvas from 'features/canvas/IAICanvas';
import IAICanvasResizer from 'features/canvas/IAICanvasResizer';
import IAICanvasResizer from 'features/canvas/components/IAICanvasResizer';
import _ from 'lodash';
import { useLayoutEffect } from 'react';
import { useAppDispatch, useAppSelector } from 'app/store';
import ImageUploadButton from 'common/components/ImageUploaderButton';
import {
canvasSelector,
setDoesCanvasNeedScaling,
} from 'features/canvas/canvasSlice';
import IAICanvas from 'features/canvas/IAICanvas';
import IAICanvasOutpaintingControls from 'features/canvas/IAICanvasOutpaintingControls';
import { setDoesCanvasNeedScaling } from 'features/canvas/store/canvasSlice';
import IAICanvas from 'features/canvas/components/IAICanvas';
import IAICanvasOutpaintingControls from 'features/canvas/components/IAICanvasToolbar/IAICanvasToolbar';
import { canvasSelector } from 'features/canvas/store/canvasSelectors';
const selector = createSelector(
[canvasSelector],

View File

@ -3,7 +3,7 @@ import UnifiedCanvasDisplay from './UnifiedCanvasDisplay';
import InvokeWorkarea from 'features/tabs/InvokeWorkarea';
import { useAppDispatch } from 'app/store';
import { useEffect } from 'react';
import { setDoesCanvasNeedScaling } from 'features/canvas/canvasSlice';
import { setDoesCanvasNeedScaling } from 'features/canvas/store/canvasSlice';
export default function UnifiedCanvasWorkarea() {
const dispatch = useAppDispatch();

View File

@ -46,7 +46,7 @@
@use '../features/tabs/TextToImage/TextToImage.scss';
@use '../features/tabs/ImageToImage/ImageToImage.scss';
@use '../features/tabs/FloatingButton.scss';
@use '../features/tabs/CanvasWorkarea.scss';
@use '../features/tabs/UnifiedCanvas/CanvasWorkarea.scss';
// Component Shared
@use '../common/components/IAINumberInput.scss';