feat(ui): display canvas generation mode in status text

- use the existing logic to determine if generation is txt2img, img2img, inpaint or outpaint
- technically `outpaint` and `inpaint` are the same, just display
"Inpaint" if its either
- debounce this by 1s to prevent jank
This commit is contained in:
psychedelicious 2023-07-23 22:34:31 +10:00
parent 00d3cd4aed
commit 28031ead70
8 changed files with 81 additions and 18 deletions

View File

@ -40,7 +40,7 @@ export const addUserInvokedCanvasListener = () => {
const state = getState(); const state = getState();
// Build canvas blobs // Build canvas blobs
const canvasBlobsAndImageData = await getCanvasData(state); const canvasBlobsAndImageData = await getCanvasData(state.canvas);
if (!canvasBlobsAndImageData) { if (!canvasBlobsAndImageData) {
log.error('Unable to create canvas data'); log.error('Unable to create canvas data');

View File

@ -2,8 +2,8 @@ import { Box, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppSelector } from 'app/store/storeHooks'; import { useAppSelector } from 'app/store/storeHooks';
import { canvasSelector } from 'features/canvas/store/canvasSelectors'; import { canvasSelector } from 'features/canvas/store/canvasSelectors';
import GenerationModeStatusText from 'features/parameters/components/Parameters/Canvas/GenerationModeStatusText';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import roundToHundreth from '../util/roundToHundreth'; import roundToHundreth from '../util/roundToHundreth';
import IAICanvasStatusTextCursorPos from './IAICanvasStatusText/IAICanvasStatusTextCursorPos'; import IAICanvasStatusTextCursorPos from './IAICanvasStatusText/IAICanvasStatusTextCursorPos';
@ -110,6 +110,7 @@ const IAICanvasStatusText = () => {
}, },
}} }}
> >
<GenerationModeStatusText />
<Box <Box
style={{ style={{
color: activeLayerColor, color: activeLayerColor,

View File

@ -2,7 +2,15 @@ import { Box, ButtonGroup, Flex } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit'; import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import IAIIconButton from 'common/components/IAIIconButton'; import IAIIconButton from 'common/components/IAIIconButton';
import IAIMantineSelect from 'common/components/IAIMantineSelect';
import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
import { useSingleAndDoubleClick } from 'common/hooks/useSingleAndDoubleClick'; import { useSingleAndDoubleClick } from 'common/hooks/useSingleAndDoubleClick';
import {
canvasCopiedToClipboard,
canvasDownloadedAsImage,
canvasMerged,
canvasSavedToGallery,
} from 'features/canvas/store/actions';
import { import {
canvasSelector, canvasSelector,
isStagingSelector, isStagingSelector,
@ -21,16 +29,8 @@ import {
} from 'features/canvas/store/canvasTypes'; } from 'features/canvas/store/canvasTypes';
import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider'; import { getCanvasBaseLayer } from 'features/canvas/util/konvaInstanceProvider';
import { systemSelector } from 'features/system/store/systemSelectors'; import { systemSelector } from 'features/system/store/systemSelectors';
import { useCopyImageToClipboard } from 'features/ui/hooks/useCopyImageToClipboard';
import { isEqual } from 'lodash-es'; import { isEqual } from 'lodash-es';
import IAIMantineSearchableSelect from 'common/components/IAIMantineSearchableSelect';
import { useImageUploadButton } from 'common/hooks/useImageUploadButton';
import {
canvasCopiedToClipboard,
canvasDownloadedAsImage,
canvasMerged,
canvasSavedToGallery,
} from 'features/canvas/store/actions';
import { useHotkeys } from 'react-hotkeys-hook'; import { useHotkeys } from 'react-hotkeys-hook';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { import {
@ -48,7 +48,6 @@ import IAICanvasRedoButton from './IAICanvasRedoButton';
import IAICanvasSettingsButtonPopover from './IAICanvasSettingsButtonPopover'; import IAICanvasSettingsButtonPopover from './IAICanvasSettingsButtonPopover';
import IAICanvasToolChooserOptions from './IAICanvasToolChooserOptions'; import IAICanvasToolChooserOptions from './IAICanvasToolChooserOptions';
import IAICanvasUndoButton from './IAICanvasUndoButton'; import IAICanvasUndoButton from './IAICanvasUndoButton';
import { useCopyImageToClipboard } from 'features/ui/hooks/useCopyImageToClipboard';
export const selector = createSelector( export const selector = createSelector(
[systemSelector, canvasSelector, isStagingSelector], [systemSelector, canvasSelector, isStagingSelector],
@ -220,7 +219,7 @@ const IAICanvasToolbar = () => {
}} }}
> >
<Box w={24}> <Box w={24}>
<IAIMantineSearchableSelect <IAIMantineSelect
tooltip={`${t('unifiedCanvas.layer')} (Q)`} tooltip={`${t('unifiedCanvas.layer')} (Q)`}
value={layer} value={layer}
data={LAYER_NAMES_DICT} data={LAYER_NAMES_DICT}

View File

@ -30,6 +30,7 @@ import {
CanvasState, CanvasState,
CanvasTool, CanvasTool,
Dimensions, Dimensions,
GenerationMode,
isCanvasAnyLine, isCanvasAnyLine,
isCanvasBaseImage, isCanvasBaseImage,
isCanvasMaskLine, isCanvasMaskLine,
@ -858,6 +859,9 @@ export const canvasSlice = createSlice({
state.isMovingBoundingBox = false; state.isMovingBoundingBox = false;
state.isTransformingBoundingBox = false; state.isTransformingBoundingBox = false;
}, },
generationModeChanged: (state, action: PayloadAction<GenerationMode>) => {
state.generationMode = action.payload;
},
}, },
extraReducers: (builder) => { extraReducers: (builder) => {
builder.addCase(sessionCanceled.pending, (state) => { builder.addCase(sessionCanceled.pending, (state) => {
@ -955,6 +959,7 @@ export const {
stagingAreaInitialized, stagingAreaInitialized,
canvasSessionIdChanged, canvasSessionIdChanged,
setShouldAntialias, setShouldAntialias,
generationModeChanged,
} = canvasSlice.actions; } = canvasSlice.actions;
export default canvasSlice.reducer; export default canvasSlice.reducer;

View File

@ -168,4 +168,7 @@ export interface CanvasState {
stageDimensions: Dimensions; stageDimensions: Dimensions;
stageScale: number; stageScale: number;
tool: CanvasTool; tool: CanvasTool;
generationMode?: GenerationMode;
} }
export type GenerationMode = 'txt2img' | 'img2img' | 'inpaint' | 'outpaint';

View File

@ -1,6 +1,5 @@
import { logger } from 'app/logging/logger'; import { logger } from 'app/logging/logger';
import { RootState } from 'app/store/store'; import { CanvasState, isCanvasMaskLine } from '../store/canvasTypes';
import { isCanvasMaskLine } from '../store/canvasTypes';
import createMaskStage from './createMaskStage'; import createMaskStage from './createMaskStage';
import { getCanvasBaseLayer, getCanvasStage } from './konvaInstanceProvider'; import { getCanvasBaseLayer, getCanvasStage } from './konvaInstanceProvider';
import { konvaNodeToBlob } from './konvaNodeToBlob'; import { konvaNodeToBlob } from './konvaNodeToBlob';
@ -9,7 +8,7 @@ import { konvaNodeToImageData } from './konvaNodeToImageData';
/** /**
* Gets Blob and ImageData objects for the base and mask layers * Gets Blob and ImageData objects for the base and mask layers
*/ */
export const getCanvasData = async (state: RootState) => { export const getCanvasData = async (canvasState: CanvasState) => {
const log = logger('canvas'); const log = logger('canvas');
const canvasBaseLayer = getCanvasBaseLayer(); const canvasBaseLayer = getCanvasBaseLayer();
@ -26,7 +25,7 @@ export const getCanvasData = async (state: RootState) => {
boundingBoxDimensions, boundingBoxDimensions,
isMaskEnabled, isMaskEnabled,
shouldPreserveMaskedArea, shouldPreserveMaskedArea,
} = state.canvas; } = canvasState;
const boundingBox = { const boundingBox = {
...boundingBoxCoordinates, ...boundingBoxCoordinates,

View File

@ -2,11 +2,12 @@ import {
areAnyPixelsBlack, areAnyPixelsBlack,
getImageDataTransparency, getImageDataTransparency,
} from 'common/util/arrayBuffer'; } from 'common/util/arrayBuffer';
import { GenerationMode } from '../store/canvasTypes';
export const getCanvasGenerationMode = ( export const getCanvasGenerationMode = (
baseImageData: ImageData, baseImageData: ImageData,
maskImageData: ImageData maskImageData: ImageData
) => { ): GenerationMode => {
const { const {
isPartiallyTransparent: baseIsPartiallyTransparent, isPartiallyTransparent: baseIsPartiallyTransparent,
isFullyTransparent: baseIsFullyTransparent, isFullyTransparent: baseIsFullyTransparent,

View File

@ -0,0 +1,55 @@
import { Box } from '@chakra-ui/react';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { generationModeChanged } from 'features/canvas/store/canvasSlice';
import { getCanvasData } from 'features/canvas/util/getCanvasData';
import { getCanvasGenerationMode } from 'features/canvas/util/getCanvasGenerationMode';
import { useDebounce } from 'react-use';
const GENERATION_MODE_NAME_MAP = {
txt2img: 'Text to Image',
img2img: 'Image to Image',
inpaint: 'Inpaint',
outpaint: 'Inpaint',
};
export const useGenerationMode = () => {
const dispatch = useAppDispatch();
const canvasState = useAppSelector((state) => state.canvas);
useDebounce(
async () => {
// Build canvas blobs
const canvasBlobsAndImageData = await getCanvasData(canvasState);
if (!canvasBlobsAndImageData) {
return;
}
const { baseImageData, maskImageData } = canvasBlobsAndImageData;
// Determine the generation mode
const generationMode = getCanvasGenerationMode(
baseImageData,
maskImageData
);
dispatch(generationModeChanged(generationMode));
},
1000,
[dispatch, canvasState, generationModeChanged]
);
};
const GenerationModeStatusText = () => {
const generationMode = useAppSelector((state) => state.canvas.generationMode);
useGenerationMode();
return (
<Box>
Mode: {generationMode ? GENERATION_MODE_NAME_MAP[generationMode] : '...'}
</Box>
);
};
export default GenerationModeStatusText;