diff --git a/backend/invoke_ai_web_server.py b/backend/invoke_ai_web_server.py index 0a3877edf0..271407381d 100644 --- a/backend/invoke_ai_web_server.py +++ b/backend/invoke_ai_web_server.py @@ -616,10 +616,7 @@ class InvokeAIWebServer: """ Prepare for generation based on generation_mode """ - if generation_parameters["generation_mode"] in [ - "outpainting", - "inpainting", - ]: + if generation_parameters["generation_mode"] == "unifiedCanvas": """ generation_parameters["init_img"] is a base64 image generation_parameters["init_mask"] is a base64 image @@ -634,35 +631,23 @@ class InvokeAIWebServer: original_bounding_box = generation_parameters["bounding_box"].copy() - if generation_parameters["generation_mode"] == "outpainting": - initial_image = dataURL_to_image( - generation_parameters["init_img"] - ).convert("RGBA") + initial_image = dataURL_to_image( + generation_parameters["init_img"] + ).convert("RGBA") - """ - The outpaint image and mask are pre-cropped by the UI, so the bounding box we pass - to the generator should be: - { - "x": 0, - "y": 0, - "width": original_bounding_box["width"], - "height": original_bounding_box["height"] - } - """ + """ + The outpaint image and mask are pre-cropped by the UI, so the bounding box we pass + to the generator should be: + { + "x": 0, + "y": 0, + "width": original_bounding_box["width"], + "height": original_bounding_box["height"] + } + """ - generation_parameters["bounding_box"]["x"] = 0 - generation_parameters["bounding_box"]["y"] = 0 - elif generation_parameters["generation_mode"] == "inpainting": - init_img_path = self.get_image_path_from_url(init_img_url) - initial_image = Image.open(init_img_path) - - """ - For inpainting, only the mask is pre-cropped by the UI, so we need to crop out a copy - of the region of the image to be inpainted to match the size of the mask image. - """ - initial_image = copy_image_from_bounding_box( - initial_image, **generation_parameters["bounding_box"] - ) + generation_parameters["bounding_box"]["x"] = 0 + generation_parameters["bounding_box"]["y"] = 0 # Convert mask dataURL to an image and convert to greyscale mask_image = dataURL_to_image( @@ -930,7 +915,7 @@ class InvokeAIWebServer: if "init_mask" in all_parameters: all_parameters["init_mask"] = "" # TODO: store the mask in metadata - if generation_parameters["generation_mode"] == "outpainting": + if generation_parameters["generation_mode"] == "unifiedCanvas": all_parameters["bounding_box"] = original_bounding_box metadata = self.parameters_to_generated_image_metadata(all_parameters) diff --git a/frontend/src/app/App.tsx b/frontend/src/app/App.tsx index 0e781ed972..c8b0c117a9 100644 --- a/frontend/src/app/App.tsx +++ b/frontend/src/app/App.tsx @@ -50,7 +50,7 @@ const appSelector = createSelector( const shouldShowGalleryButton = !(shouldShowGallery || (shouldHoldGalleryOpen && !shouldPinGallery)) && - ['txt2img', 'img2img', 'inpainting', 'outpainting'].includes( + ['txt2img', 'img2img', 'unifiedCanvas'].includes( activeTabName ); @@ -59,7 +59,7 @@ const appSelector = createSelector( shouldShowOptionsPanel || (shouldHoldOptionsPanelOpen && !shouldPinOptionsPanel) ) && - ['txt2img', 'img2img', 'inpainting', 'outpainting'].includes( + ['txt2img', 'img2img', 'unifiedCanvas'].includes( activeTabName ); diff --git a/frontend/src/app/selectors/readinessSelector.ts b/frontend/src/app/selectors/readinessSelector.ts index 1b3ca83ca8..b8ca488201 100644 --- a/frontend/src/app/selectors/readinessSelector.ts +++ b/frontend/src/app/selectors/readinessSelector.ts @@ -44,11 +44,6 @@ export const readinessSelector = createSelector( reasonsWhyNotReady.push('No initial image selected'); } - if (activeTabName === 'inpainting' && !initialCanvasImage) { - isReady = false; - reasonsWhyNotReady.push('No inpainting image selected'); - } - // TODO: job queue // Cannot generate if already processing an image if (isProcessing) { diff --git a/frontend/src/app/socketio/emitters.ts b/frontend/src/app/socketio/emitters.ts index f72e9697e8..651180d32a 100644 --- a/frontend/src/app/socketio/emitters.ts +++ b/frontend/src/app/socketio/emitters.ts @@ -54,24 +54,26 @@ const makeSocketIOEmitters = ( systemState, }; - if (generationMode === 'inpainting') { - const initialCanvasImage = initialCanvasImageSelector(getState()); - const imageUrl = initialCanvasImage?.image.url; + // if (generationMode === 'inpainting') { + // const initialCanvasImage = initialCanvasImageSelector(getState()); + // const imageUrl = initialCanvasImage?.image.url; - if (!imageUrl) { - dispatch( - addLogEntry({ - timestamp: dateFormat(new Date(), 'isoDateTime'), - message: 'Inpainting image not loaded, cannot generate image.', - level: 'error', - }) - ); - dispatch(errorOccurred()); - return; - } + // if (!imageUrl) { + // dispatch( + // addLogEntry({ + // timestamp: dateFormat(new Date(), 'isoDateTime'), + // message: 'Inpainting image not loaded, cannot generate image.', + // level: 'error', + // }) + // ); + // dispatch(errorOccurred()); + // return; + // } - frontendToBackendParametersConfig.imageToProcessUrl = imageUrl; - } else if (!['txt2img', 'img2img'].includes(generationMode)) { + // frontendToBackendParametersConfig.imageToProcessUrl = imageUrl; + // } else + + if (!['txt2img', 'img2img'].includes(generationMode)) { if (!galleryState.currentImage?.url) return; frontendToBackendParametersConfig.imageToProcessUrl = diff --git a/frontend/src/app/socketio/listeners.ts b/frontend/src/app/socketio/listeners.ts index 842129d267..1c6dc7dd1f 100644 --- a/frontend/src/app/socketio/listeners.ts +++ b/frontend/src/app/socketio/listeners.ts @@ -37,10 +37,7 @@ import { requestNewImages, requestSystemConfig, } from './actions'; -import { - addImageToStagingArea, - setImageToInpaint, -} from 'features/canvas/canvasSlice'; +import { addImageToStagingArea } from 'features/canvas/canvasSlice'; import { tabMap } from 'features/tabs/InvokeTabs'; /** @@ -120,23 +117,15 @@ const makeSocketIOListeners = ( ); } - if ( - ['inpainting', 'outpainting'].includes(generationMode) && - data.boundingBox - ) { + if (generationMode === 'unifiedCanvas' && data.boundingBox) { newImage.category = 'temp'; const { boundingBox } = data; - - if (generationMode === 'inpainting') { - dispatch(setImageToInpaint(newImage)); - } else { - dispatch( - addImageToStagingArea({ - image: newImage, - boundingBox, - }) - ); - } + dispatch( + addImageToStagingArea({ + image: newImage, + boundingBox, + }) + ); } if (shouldLoopback) { @@ -146,10 +135,6 @@ const makeSocketIOListeners = ( dispatch(setInitialImage(newImage)); break; } - case 'inpainting': { - dispatch(setImageToInpaint(newImage)); - break; - } } } diff --git a/frontend/src/app/store.ts b/frontend/src/app/store.ts index 4af37c195b..836f4954f5 100644 --- a/frontend/src/app/store.ts +++ b/frontend/src/app/store.ts @@ -28,14 +28,8 @@ import { socketioMiddleware } from './socketio/middleware'; * The necesssary nested persistors with blacklists are configured below. */ -const genericCanvasBlacklist = ['cursorPosition']; - -const inpaintingCanvasBlacklist = genericCanvasBlacklist.map( - (blacklistItem) => `canvas.inpainting.${blacklistItem}` -); - -const outpaintingCanvasBlacklist = genericCanvasBlacklist.map( - (blacklistItem) => `canvas.outpainting.${blacklistItem}` +const canvasBlacklist = ['cursorPosition'].map( + (blacklistItem) => `canvas.${blacklistItem}` ); const systemBlacklist = [ @@ -73,12 +67,7 @@ const rootPersistConfig = getPersistConfig({ key: 'root', storage, rootReducer, - blacklist: [ - ...inpaintingCanvasBlacklist, - ...outpaintingCanvasBlacklist, - ...systemBlacklist, - ...galleryBlacklist, - ], + blacklist: [...canvasBlacklist, ...systemBlacklist, ...galleryBlacklist], debounce: 300, }); diff --git a/frontend/src/common/components/ImageUploader.tsx b/frontend/src/common/components/ImageUploader.tsx index 6427736df1..a7afda2b96 100644 --- a/frontend/src/common/components/ImageUploader.tsx +++ b/frontend/src/common/components/ImageUploader.tsx @@ -135,7 +135,7 @@ const ImageUploader = (props: ImageUploaderProps) => { }; }, [dispatch, toast, activeTabName]); - const overlaySecondaryText = ['img2img', 'inpainting'].includes(activeTabName) + const overlaySecondaryText = ['img2img', 'unifiedCanvas'].includes(activeTabName) ? ` to ${tabDict[activeTabName as keyof typeof tabDict].tooltip}` : ``; diff --git a/frontend/src/common/icons/UnifiedCanvas.afdesign b/frontend/src/common/icons/UnifiedCanvas.afdesign new file mode 100644 index 0000000000..d4f7b149ce Binary files /dev/null and b/frontend/src/common/icons/UnifiedCanvas.afdesign differ diff --git a/frontend/src/common/icons/UnifiedCanvasIcon.tsx b/frontend/src/common/icons/UnifiedCanvasIcon.tsx new file mode 100644 index 0000000000..daa8ecf8a9 --- /dev/null +++ b/frontend/src/common/icons/UnifiedCanvasIcon.tsx @@ -0,0 +1,16 @@ +import { createIcon } from '@chakra-ui/react'; + +const UnifiedCanvasIcon = createIcon({ + displayName: 'UnifiedCanvasIcon', + viewBox: '0 0 3544 3544', + path: ( + + ), +}); + +export default UnifiedCanvasIcon; diff --git a/frontend/src/common/icons/design_files/UnifiedCanvas.afdesign b/frontend/src/common/icons/design_files/UnifiedCanvas.afdesign new file mode 100644 index 0000000000..4248d3a41d Binary files /dev/null and b/frontend/src/common/icons/design_files/UnifiedCanvas.afdesign differ diff --git a/frontend/src/common/icons/design_files/UnifiedCanvas.svg b/frontend/src/common/icons/design_files/UnifiedCanvas.svg new file mode 100644 index 0000000000..fe9a9b800e --- /dev/null +++ b/frontend/src/common/icons/design_files/UnifiedCanvas.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/frontend/src/common/util/parameterTranslation.ts b/frontend/src/common/util/parameterTranslation.ts index 88177e3eef..b00157266b 100644 --- a/frontend/src/common/util/parameterTranslation.ts +++ b/frontend/src/common/util/parameterTranslation.ts @@ -106,10 +106,7 @@ export const frontendToBackendParameters = ( } // inpainting exclusive parameters - if ( - ['inpainting', 'outpainting'].includes(generationMode) && - canvasImageLayerRef.current - ) { + if (generationMode === 'unifiedCanvas' && canvasImageLayerRef.current) { const { layerState: { objects }, boundingBoxCoordinates, @@ -146,44 +143,42 @@ export const frontendToBackendParameters = ( generationParameters.bounding_box = boundingBox; - if (generationMode === 'outpainting') { - const tempScale = canvasImageLayerRef.current.scale(); + const tempScale = canvasImageLayerRef.current.scale(); - canvasImageLayerRef.current.scale({ - x: 1 / stageScale, - y: 1 / stageScale, - }); + canvasImageLayerRef.current.scale({ + x: 1 / stageScale, + y: 1 / stageScale, + }); - const absPos = canvasImageLayerRef.current.getAbsolutePosition(); + const absPos = canvasImageLayerRef.current.getAbsolutePosition(); - const imageDataURL = canvasImageLayerRef.current.toDataURL({ - x: boundingBox.x + absPos.x, - y: boundingBox.y + absPos.y, - width: boundingBox.width, - height: boundingBox.height, - }); + const imageDataURL = canvasImageLayerRef.current.toDataURL({ + x: boundingBox.x + absPos.x, + y: boundingBox.y + absPos.y, + width: boundingBox.width, + height: boundingBox.height, + }); - if (enableImageDebugging) { - openBase64ImageInTab([ - { base64: maskDataURL, caption: 'mask sent as init_mask' }, - { base64: imageDataURL, caption: 'image sent as init_img' }, - ]); - } - - canvasImageLayerRef.current.scale(tempScale); - - generationParameters.init_img = imageDataURL; - - // TODO: The server metadata generation needs to be changed to fix this. - generationParameters.progress_images = false; - - generationParameters.seam_size = 96; - generationParameters.seam_blur = 16; - generationParameters.seam_strength = 0.7; - generationParameters.seam_steps = 10; - generationParameters.tile_size = 32; - generationParameters.force_outpaint = false; + if (enableImageDebugging) { + openBase64ImageInTab([ + { base64: maskDataURL, caption: 'mask sent as init_mask' }, + { base64: imageDataURL, caption: 'image sent as init_img' }, + ]); } + + canvasImageLayerRef.current.scale(tempScale); + + generationParameters.init_img = imageDataURL; + + // TODO: The server metadata generation needs to be changed to fix this. + generationParameters.progress_images = false; + + generationParameters.seam_size = 96; + generationParameters.seam_blur = 16; + generationParameters.seam_strength = 0.7; + generationParameters.seam_steps = 10; + generationParameters.tile_size = 32; + generationParameters.force_outpaint = false; } if (shouldGenerateVariations) { diff --git a/frontend/src/features/canvas/IAICanvas.tsx b/frontend/src/features/canvas/IAICanvas.tsx index 2fd89162b6..1620b132ac 100644 --- a/frontend/src/features/canvas/IAICanvas.tsx +++ b/frontend/src/features/canvas/IAICanvas.tsx @@ -93,7 +93,6 @@ const selector = createSelector( stageDimensions, stageScale, tool, - isOnOutpaintingTab: activeTabName === 'outpainting', isStaging, shouldShowIntermediates, shouldLockToInitialImage, @@ -122,7 +121,6 @@ const IAICanvas = () => { stageDimensions, stageScale, tool, - isOnOutpaintingTab, isStaging, shouldShowIntermediates, shouldLockToInitialImage, @@ -207,11 +205,7 @@ const IAICanvas = () => { onDragEnd={handleDragEnd} onWheel={handleWheel} listening={(tool === 'move' || isStaging) && !isModifyingBoundingBox} - draggable={ - (tool === 'move' || isStaging) && - !isModifyingBoundingBox && - isOnOutpaintingTab - } + draggable={(tool === 'move' || isStaging) && !isModifyingBoundingBox} > @@ -243,7 +237,7 @@ const IAICanvas = () => { /> - {isOnOutpaintingTab && } + diff --git a/frontend/src/features/canvas/IAICanvasControls/IAICanvasBrushControl.tsx b/frontend/src/features/canvas/IAICanvasControls/IAICanvasBrushControl.tsx index dbd2609d6e..d0e0d736ae 100644 --- a/frontend/src/features/canvas/IAICanvasControls/IAICanvasBrushControl.tsx +++ b/frontend/src/features/canvas/IAICanvasControls/IAICanvasBrushControl.tsx @@ -18,7 +18,7 @@ import { import _ from 'lodash'; import IAICanvasMaskColorPicker from './IAICanvasMaskControls/IAICanvasMaskColorPicker'; -const inpaintingBrushSelector = createSelector( +const selector = createSelector( [canvasSelector, activeTabNameSelector], (canvas, activeTabName) => { const { tool, brushSize } = canvas; @@ -38,9 +38,7 @@ const inpaintingBrushSelector = createSelector( export default function IAICanvasBrushControl() { const dispatch = useAppDispatch(); - const { tool, brushSize, activeTabName } = useAppSelector( - inpaintingBrushSelector - ); + const { tool, brushSize, activeTabName } = useAppSelector(selector); const handleSelectBrushTool = () => dispatch(setTool('brush')); diff --git a/frontend/src/features/canvas/IAICanvasControls/IAICanvasClearImageControl.tsx b/frontend/src/features/canvas/IAICanvasControls/IAICanvasClearImageControl.tsx deleted file mode 100644 index af04cc35f7..0000000000 --- a/frontend/src/features/canvas/IAICanvasControls/IAICanvasClearImageControl.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { FaTrash } from 'react-icons/fa'; -import { useAppDispatch } from 'app/store'; -import IAIIconButton from 'common/components/IAIIconButton'; -import { clearImageToInpaint } from 'features/canvas/canvasSlice'; - -export default function IAICanvasClearImageControl() { - const dispatch = useAppDispatch(); - - const handleClearImage = () => { - dispatch(clearImageToInpaint()); - }; - - return ( - } - onClick={handleClearImage} - /> - ); -} diff --git a/frontend/src/features/canvas/IAICanvasControls/IAICanvasMaskControls/IAICanvasMaskVisibilityControl.tsx b/frontend/src/features/canvas/IAICanvasControls/IAICanvasMaskControls/IAICanvasMaskVisibilityControl.tsx index 5bc2c36201..f54b8bf08f 100644 --- a/frontend/src/features/canvas/IAICanvasControls/IAICanvasMaskControls/IAICanvasMaskVisibilityControl.tsx +++ b/frontend/src/features/canvas/IAICanvasControls/IAICanvasMaskControls/IAICanvasMaskVisibilityControl.tsx @@ -40,7 +40,7 @@ export default function IAICanvasMaskVisibilityControl() { handleToggleShouldShowMask(); }, { - enabled: activeTabName === 'inpainting' || activeTabName == 'outpainting', + enabled: activeTabName === 'unifiedCanvas', }, [activeTabName, isMaskEnabled] ); diff --git a/frontend/src/features/canvas/IAICanvasEraserLines.tsx b/frontend/src/features/canvas/IAICanvasEraserLines.tsx deleted file mode 100644 index 9513ac0fef..0000000000 --- a/frontend/src/features/canvas/IAICanvasEraserLines.tsx +++ /dev/null @@ -1,49 +0,0 @@ -// import { GroupConfig } from 'konva/lib/Group'; -// import { Group, Line } from 'react-konva'; -// import { RootState, useAppSelector } from 'app/store'; -// import { createSelector } from '@reduxjs/toolkit'; -// import { OutpaintingCanvasState } from './canvasSlice'; - -// export const canvasEraserLinesSelector = createSelector( -// (state: RootState) => state.canvas.outpainting, -// (outpainting: OutpaintingCanvasState) => { -// const { eraserLines } = outpainting; -// return { -// eraserLines, -// }; -// } -// ); - -// type IAICanvasEraserLinesProps = GroupConfig; - -// /** -// * Draws the lines which comprise the mask. -// * -// * Uses globalCompositeOperation to handle the brush and eraser tools. -// */ -// const IAICanvasEraserLines = (props: IAICanvasEraserLinesProps) => { -// const { ...rest } = props; -// const { eraserLines } = useAppSelector(canvasEraserLinesSelector); - -// return ( -// -// {eraserLines.map((line, i) => ( -// 0 -// strokeWidth={line.strokeWidth * 2} -// tension={0} -// lineCap="round" -// lineJoin="round" -// shadowForStrokeEnabled={false} -// listening={false} -// globalCompositeOperation={'source-over'} -// /> -// ))} -// -// ); -// }; - -// export default IAICanvasEraserLines; -export default {} \ No newline at end of file diff --git a/frontend/src/features/canvas/IAICanvasResizer.tsx b/frontend/src/features/canvas/IAICanvasResizer.tsx index 483bc47abc..28bc02c5b1 100644 --- a/frontend/src/features/canvas/IAICanvasResizer.tsx +++ b/frontend/src/features/canvas/IAICanvasResizer.tsx @@ -52,9 +52,6 @@ const IAICanvasResizer = () => { if (!initialCanvasImage?.image) return; - const { width: imageWidth, height: imageHeight } = - initialCanvasImage.image; - dispatch( setCanvasContainerDimensions({ width: clientWidth, @@ -69,34 +66,6 @@ const IAICanvasResizer = () => { } dispatch(setDoesCanvasNeedScaling(false)); - // } - // if ((activeTabName === 'inpainting') && initialCanvasImage?.image) { - // const { width: imageWidth, height: imageHeight } = - // initialCanvasImage.image; - - // const scale = Math.min( - // 1, - // Math.min(clientWidth / imageWidth, clientHeight / imageHeight) - // ); - - // dispatch(setStageScale(scale)); - - // dispatch( - // setStageDimensions({ - // width: Math.floor(imageWidth * scale), - // height: Math.floor(imageHeight * scale), - // }) - // ); - // dispatch(setDoesCanvasNeedScaling(false)); - // } else if (activeTabName === 'outpainting') { - // dispatch( - // setStageDimensions({ - // width: Math.floor(clientWidth), - // height: Math.floor(clientHeight), - // }) - // ); - // dispatch(setDoesCanvasNeedScaling(false)); - // } }, 0); }, [ dispatch, @@ -104,6 +73,7 @@ const IAICanvasResizer = () => { doesCanvasNeedScaling, activeTabName, isCanvasInitialized, + shouldLockToInitialImage, ]); return ( diff --git a/frontend/src/features/canvas/canvasReducers.ts b/frontend/src/features/canvas/canvasReducers.ts index e70bfddb55..bff50eebc4 100644 --- a/frontend/src/features/canvas/canvasReducers.ts +++ b/frontend/src/features/canvas/canvasReducers.ts @@ -6,7 +6,7 @@ import { } from 'common/util/roundDownToMultiple'; import _ from 'lodash'; -export const setInitialCanvasImage = ( +export const setInitialCanvasImage_reducer = ( state: CanvasState, image: InvokeAI.Image ) => { diff --git a/frontend/src/features/canvas/canvasSlice.ts b/frontend/src/features/canvas/canvasSlice.ts index 13f8f5ae14..8e86b7c8c2 100644 --- a/frontend/src/features/canvas/canvasSlice.ts +++ b/frontend/src/features/canvas/canvasSlice.ts @@ -8,13 +8,11 @@ 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 } from './canvasReducers'; +import { setInitialCanvasImage_reducer } from './canvasReducers'; import calculateScale from './util/calculateScale'; import calculateCoordinates from './util/calculateCoordinates'; import floorCoordinates from './util/floorCoordinates'; -export type CanvasMode = 'inpainting' | 'outpainting'; - export type CanvasLayer = 'base' | 'mask'; export type CanvasDrawingTool = 'brush' | 'eraser'; @@ -256,15 +254,8 @@ export const canvasSlice = createSlice({ setCursorPosition: (state, action: PayloadAction) => { state.cursorPosition = action.payload; }, - clearImageToInpaint: (state) => { - // TODO - // state.inpainting.imageToInpaint = undefined; - }, - setImageToOutpaint: (state, action: PayloadAction) => { - setInitialCanvasImage(state, action.payload); - }, - setImageToInpaint: (state, action: PayloadAction) => { - setInitialCanvasImage(state, action.payload); + setInitialCanvasImage: (state, action: PayloadAction) => { + setInitialCanvasImage_reducer(state, action.payload); }, setStageDimensions: (state, action: PayloadAction) => { state.stageDimensions = action.payload; @@ -642,10 +633,8 @@ export const canvasSlice = createSlice({ if (kind !== 'init') return; - if (activeTabName === 'inpainting') { - setInitialCanvasImage(state, image); - } else if (activeTabName === 'outpainting') { - setInitialCanvasImage(state, image); + if (activeTabName === 'unifiedCanvas') { + setInitialCanvasImage_reducer(state, image); } }); }, @@ -665,13 +654,11 @@ export const { setShouldShowBrushPreview, setMaskColor, clearMask, - clearImageToInpaint, undo, redo, setCursorPosition, setStageDimensions, - setImageToInpaint, - setImageToOutpaint, + setInitialCanvasImage, setBoundingBoxDimensions, setBoundingBoxCoordinates, setBoundingBoxPreviewFill, diff --git a/frontend/src/features/canvas/hooks/useCanvasHotkeys.ts b/frontend/src/features/canvas/hooks/useCanvasHotkeys.ts index dcb7265b85..870c8b0ffb 100644 --- a/frontend/src/features/canvas/hooks/useCanvasHotkeys.ts +++ b/frontend/src/features/canvas/hooks/useCanvasHotkeys.ts @@ -13,7 +13,7 @@ import { canvasSelector } from '../canvasSlice'; import { useRef } from 'react'; import { stageRef } from '../IAICanvas'; -const inpaintingCanvasHotkeysSelector = createSelector( +const selector = createSelector( [canvasSelector, activeTabNameSelector], (canvas, activeTabName) => { const { @@ -40,9 +40,8 @@ const inpaintingCanvasHotkeysSelector = createSelector( const useInpaintingCanvasHotkeys = () => { const dispatch = useAppDispatch(); - const { activeTabName, shouldShowBoundingBox, tool } = useAppSelector( - inpaintingCanvasHotkeysSelector - ); + const { activeTabName, shouldShowBoundingBox, tool } = + useAppSelector(selector); const previousToolRef = useRef(null); // Toggle lock bounding box diff --git a/frontend/src/features/canvas/hooks/useCanvasZoom.ts b/frontend/src/features/canvas/hooks/useCanvasZoom.ts index 1674b6663c..7f8ac5a50c 100644 --- a/frontend/src/features/canvas/hooks/useCanvasZoom.ts +++ b/frontend/src/features/canvas/hooks/useCanvasZoom.ts @@ -60,12 +60,7 @@ const useCanvasWheel = (stageRef: MutableRefObject) => { return useCallback( (e: KonvaEventObject) => { // stop default scrolling - if ( - activeTabName !== 'outpainting' || - !stageRef.current || - isMoveStageKeyHeld || - !initialCanvasImage - ) + if (!stageRef.current || isMoveStageKeyHeld || !initialCanvasImage) return; e.evt.preventDefault(); diff --git a/frontend/src/features/gallery/CurrentImageButtons.tsx b/frontend/src/features/gallery/CurrentImageButtons.tsx index 426c8674c1..c84f7252c0 100644 --- a/frontend/src/features/gallery/CurrentImageButtons.tsx +++ b/frontend/src/features/gallery/CurrentImageButtons.tsx @@ -36,9 +36,9 @@ import { FaTrash, } from 'react-icons/fa'; import { - setImageToInpaint, setDoesCanvasNeedScaling, - setImageToOutpaint, + setInitialCanvasImage, + setShouldLockToInitialImage, } from 'features/canvas/canvasSlice'; import { GalleryState } from './gallerySlice'; import { activeTabNameSelector } from 'features/options/optionsSelectors'; @@ -320,12 +320,12 @@ const CurrentImageButtons = () => { if (!currentImage) return; if (isLightBoxOpen) dispatch(setIsLightBoxOpen(false)); - dispatch(setImageToInpaint(currentImage)); - dispatch(setActiveTab('inpainting')); + dispatch(setInitialCanvasImage(currentImage)); + dispatch(setShouldLockToInitialImage(true)); dispatch(setDoesCanvasNeedScaling(true)); toast({ - title: 'Sent to Inpainting', + title: 'Sent to Unified Canvas', status: 'success', duration: 2500, isClosable: true, @@ -336,12 +336,12 @@ const CurrentImageButtons = () => { if (!currentImage) return; if (isLightBoxOpen) dispatch(setIsLightBoxOpen(false)); - dispatch(setImageToOutpaint(currentImage)); - dispatch(setActiveTab('outpainting')); + dispatch(setInitialCanvasImage(currentImage)); + dispatch(setShouldLockToInitialImage(false)); dispatch(setDoesCanvasNeedScaling(true)); toast({ - title: 'Sent to Inpainting', + title: 'Sent to Unified Canvas', status: 'success', duration: 2500, isClosable: true, diff --git a/frontend/src/features/gallery/HoverableImage.tsx b/frontend/src/features/gallery/HoverableImage.tsx index d7896ce367..11763fb85f 100644 --- a/frontend/src/features/gallery/HoverableImage.tsx +++ b/frontend/src/features/gallery/HoverableImage.tsx @@ -23,8 +23,9 @@ import { import * as InvokeAI from 'app/invokeai'; import * as ContextMenu from '@radix-ui/react-context-menu'; import { - setImageToInpaint, - setImageToOutpaint, + setDoesCanvasNeedScaling, + setInitialCanvasImage, + setShouldLockToInitialImage, } from 'features/canvas/canvasSlice'; import { hoverableImageSelector } from './gallerySliceSelectors'; @@ -97,10 +98,15 @@ const HoverableImage = memo((props: HoverableImageProps) => { const handleSendToInpainting = () => { if (isLightBoxOpen) dispatch(setIsLightBoxOpen(false)); - dispatch(setImageToInpaint(image)); - if (activeTabName !== 'inpainting') { - dispatch(setActiveTab('inpainting')); + + dispatch(setInitialCanvasImage(image)); + dispatch(setShouldLockToInitialImage(true)); + dispatch(setDoesCanvasNeedScaling(true)); + + if (activeTabName !== 'unifiedCanvas') { + dispatch(setActiveTab('unifiedCanvas')); } + toast({ title: 'Sent to Inpainting', status: 'success', @@ -111,10 +117,15 @@ const HoverableImage = memo((props: HoverableImageProps) => { const handleSendToOutpainting = () => { if (isLightBoxOpen) dispatch(setIsLightBoxOpen(false)); - dispatch(setImageToOutpaint(image)); - if (activeTabName !== 'outpainting') { - dispatch(setActiveTab('outpainting')); + + dispatch(setInitialCanvasImage(image)); + dispatch(setShouldLockToInitialImage(true)); + dispatch(setDoesCanvasNeedScaling(true)); + + if (activeTabName !== 'unifiedCanvas') { + dispatch(setActiveTab('unifiedCanvas')); } + toast({ title: 'Sent to Outpainting', status: 'success', diff --git a/frontend/src/features/gallery/ImageGallery.tsx b/frontend/src/features/gallery/ImageGallery.tsx index 321f62325d..41c549132d 100644 --- a/frontend/src/features/gallery/ImageGallery.tsx +++ b/frontend/src/features/gallery/ImageGallery.tsx @@ -76,7 +76,7 @@ export default function ImageGallery() { return; } - if (activeTabName === 'inpainting' || activeTabName === 'outpainting') { + if (activeTabName === 'unifiedCanvas') { dispatch(setGalleryWidth(190)); setGalleryMinWidth(190); setGalleryMaxWidth(190); diff --git a/frontend/src/features/options/MainOptions/MainHeight.tsx b/frontend/src/features/options/MainOptions/MainHeight.tsx index 61ba0eb25c..c5dc0d28ac 100644 --- a/frontend/src/features/options/MainOptions/MainHeight.tsx +++ b/frontend/src/features/options/MainOptions/MainHeight.tsx @@ -15,7 +15,7 @@ export default function MainHeight() { return ( - {renderHotkeyModalItems(inpaintingHotkeys)} - - - - - - Outpainting Hotkeys - - - - {renderHotkeyModalItems(outpaintingHotkeys)} + {renderHotkeyModalItems(unifiedCanvasHotkeys)} diff --git a/frontend/src/features/tabs/Inpainting/InpaintingDisplay.tsx b/frontend/src/features/tabs/Inpainting/InpaintingDisplay.tsx deleted file mode 100644 index 0e8afb8888..0000000000 --- a/frontend/src/features/tabs/Inpainting/InpaintingDisplay.tsx +++ /dev/null @@ -1,80 +0,0 @@ -import { createSelector } from '@reduxjs/toolkit'; -import IAICanvasControls from 'features/canvas/IAICanvasControls'; -import IAICanvasResizer from 'features/canvas/IAICanvasResizer'; -import _ from 'lodash'; -import { useLayoutEffect } from 'react'; -import { RootState, useAppDispatch, useAppSelector } from 'app/store'; -import ImageUploadButton from 'common/components/ImageUploaderButton'; -import CurrentImageDisplay from 'features/gallery/CurrentImageDisplay'; -import { OptionsState } from 'features/options/optionsSlice'; -import { - initialCanvasImageSelector, - CanvasState, - setDoesCanvasNeedScaling, -} from 'features/canvas/canvasSlice'; -import IAICanvas from 'features/canvas/IAICanvas'; - -const inpaintingDisplaySelector = createSelector( - [ - initialCanvasImageSelector, - (state: RootState) => state.canvas, - (state: RootState) => state.options, - ], - (initialCanvasImage, canvas: CanvasState, options: OptionsState) => { - const { doesCanvasNeedScaling } = canvas; - const { showDualDisplay } = options; - return { - doesCanvasNeedScaling, - showDualDisplay, - initialCanvasImage, - }; - }, - { - memoizeOptions: { - resultEqualityCheck: _.isEqual, - }, - } -); - -const InpaintingDisplay = () => { - const dispatch = useAppDispatch(); - const { showDualDisplay, doesCanvasNeedScaling, initialCanvasImage } = - useAppSelector(inpaintingDisplaySelector); - - useLayoutEffect(() => { - const resizeCallback = _.debounce( - () => dispatch(setDoesCanvasNeedScaling(true)), - 250 - ); - window.addEventListener('resize', resizeCallback); - return () => window.removeEventListener('resize', resizeCallback); - }, [dispatch]); - - const inpaintingComponent = initialCanvasImage ? ( - - - - {doesCanvasNeedScaling ? : } - - - ) : ( - - ); - - return ( - - {inpaintingComponent} - {showDualDisplay && ( - - - - )} - - ); -}; - -export default InpaintingDisplay; diff --git a/frontend/src/features/tabs/Inpainting/InpaintingPanel.tsx b/frontend/src/features/tabs/Inpainting/InpaintingPanel.tsx deleted file mode 100644 index fc2500428e..0000000000 --- a/frontend/src/features/tabs/Inpainting/InpaintingPanel.tsx +++ /dev/null @@ -1,65 +0,0 @@ -// import { Feature } from 'app/features'; -import { Feature } from 'app/features'; -import { RootState, useAppSelector } from 'app/store'; -import FaceRestoreHeader from 'features/options/AdvancedOptions/FaceRestore/FaceRestoreHeader'; -import FaceRestoreOptions from 'features/options/AdvancedOptions/FaceRestore/FaceRestoreOptions'; -import ImageToImageStrength from 'features/options/AdvancedOptions/ImageToImage/ImageToImageStrength'; -import InpaintingSettings from 'features/options/AdvancedOptions/Inpainting/InpaintingSettings'; -import SeedHeader from 'features/options/AdvancedOptions/Seed/SeedHeader'; -import SeedOptions from 'features/options/AdvancedOptions/Seed/SeedOptions'; -import UpscaleHeader from 'features/options/AdvancedOptions/Upscale/UpscaleHeader'; -import UpscaleOptions from 'features/options/AdvancedOptions/Upscale/UpscaleOptions'; -import VariationsHeader from 'features/options/AdvancedOptions/Variations/VariationsHeader'; -import VariationsOptions from 'features/options/AdvancedOptions/Variations/VariationsOptions'; -import MainAdvancedOptionsCheckbox from 'features/options/MainOptions/MainAdvancedOptionsCheckbox'; -import MainOptions from 'features/options/MainOptions/MainOptions'; -import OptionsAccordion from 'features/options/OptionsAccordion'; -import ProcessButtons from 'features/options/ProcessButtons/ProcessButtons'; -import PromptInput from 'features/options/PromptInput/PromptInput'; -import InvokeOptionsPanel from 'features/tabs/InvokeOptionsPanel'; - -export default function InpaintingPanel() { - const showAdvancedOptions = useAppSelector( - (state: RootState) => state.options.showAdvancedOptions - ); - - const imageToImageAccordions = { - seed: { - header: , - feature: Feature.SEED, - options: , - }, - variations: { - header: , - feature: Feature.VARIATIONS, - options: , - }, - face_restore: { - header: , - feature: Feature.FACE_CORRECTION, - options: , - }, - upscale: { - header: , - feature: Feature.UPSCALE, - options: , - }, - }; - - return ( - - - - - - - - {showAdvancedOptions && ( - - )} - - ); -} diff --git a/frontend/src/features/tabs/InvokeTabs.tsx b/frontend/src/features/tabs/InvokeTabs.tsx index 518a542958..6b17c007e8 100644 --- a/frontend/src/features/tabs/InvokeTabs.tsx +++ b/frontend/src/features/tabs/InvokeTabs.tsx @@ -17,12 +17,12 @@ import { setShouldShowOptionsPanel, } from 'features/options/optionsSlice'; import ImageToImageWorkarea from './ImageToImage'; -import InpaintingWorkarea from './Inpainting/InpaintingWorkarea'; import TextToImageWorkarea from './TextToImage'; import Lightbox from 'features/lightbox/Lightbox'; import { setDoesCanvasNeedScaling } from 'features/canvas/canvasSlice'; -import OutpaintingWorkarea from './Outpainting/OutpaintingWorkarea'; +import UnifiedCanvasWorkarea from './UnifiedCanvas/UnifiedCanvasWorkarea'; import { setShouldShowGallery } from 'features/gallery/gallerySlice'; +import UnifiedCanvasIcon from 'common/icons/UnifiedCanvasIcon'; export const tabDict = { txt2img: { @@ -35,15 +35,10 @@ export const tabDict = { workarea: , tooltip: 'Image To Image', }, - inpainting: { - title: , - workarea: , - tooltip: 'Inpainting', - }, - outpainting: { - title: , - workarea: , - tooltip: 'Outpainting', + unifiedCanvas: { + title: , + workarea: , + tooltip: 'Unified Canvas', }, nodes: { title: , diff --git a/frontend/src/features/tabs/Outpainting/OutpaintingWorkarea.tsx b/frontend/src/features/tabs/Outpainting/OutpaintingWorkarea.tsx deleted file mode 100644 index 02d689de52..0000000000 --- a/frontend/src/features/tabs/Outpainting/OutpaintingWorkarea.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import OutpaintingPanel from './OutpaintingPanel'; -import OutpaintingDisplay from './OutpaintingDisplay'; -import InvokeWorkarea from 'features/tabs/InvokeWorkarea'; -import { useAppDispatch } from 'app/store'; -import { useEffect } from 'react'; -import { setDoesCanvasNeedScaling } from 'features/canvas/canvasSlice'; - -export default function OutpaintingWorkarea() { - const dispatch = useAppDispatch(); - useEffect(() => { - dispatch(setDoesCanvasNeedScaling(true)); - }, [dispatch]); - return ( - } - styleClass="inpainting-workarea-overrides" - > - - - ); -} diff --git a/frontend/src/features/tabs/Outpainting/OutpaintingDisplay.tsx b/frontend/src/features/tabs/UnifiedCanvas/UnifiedCanvasDisplay.tsx similarity index 71% rename from frontend/src/features/tabs/Outpainting/OutpaintingDisplay.tsx rename to frontend/src/features/tabs/UnifiedCanvas/UnifiedCanvasDisplay.tsx index 186abe0328..ad8cb8634b 100644 --- a/frontend/src/features/tabs/Outpainting/OutpaintingDisplay.tsx +++ b/frontend/src/features/tabs/UnifiedCanvas/UnifiedCanvasDisplay.tsx @@ -12,7 +12,7 @@ import { import IAICanvas from 'features/canvas/IAICanvas'; import IAICanvasOutpaintingControls from 'features/canvas/IAICanvasOutpaintingControls'; -const outpaintingDisplaySelector = createSelector( +const selector = createSelector( [canvasSelector], (canvas) => { const { @@ -31,11 +31,10 @@ const outpaintingDisplaySelector = createSelector( } ); -const OutpaintingDisplay = () => { +const UnifiedCanvasDisplay = () => { const dispatch = useAppDispatch(); - const { doesCanvasNeedScaling, doesOutpaintingHaveObjects } = useAppSelector( - outpaintingDisplaySelector - ); + const { doesCanvasNeedScaling, doesOutpaintingHaveObjects } = + useAppSelector(selector); useLayoutEffect(() => { const resizeCallback = _.debounce( @@ -46,17 +45,6 @@ const OutpaintingDisplay = () => { return () => window.removeEventListener('resize', resizeCallback); }, [dispatch]); - const outpaintingComponent = doesOutpaintingHaveObjects ? ( - - - - {doesCanvasNeedScaling ? : } - - - ) : ( - - ); - return ( @@ -67,9 +55,8 @@ const OutpaintingDisplay = () => { - {/* {outpaintingComponent} */} ); }; -export default OutpaintingDisplay; +export default UnifiedCanvasDisplay; diff --git a/frontend/src/features/tabs/Outpainting/OutpaintingPanel.tsx b/frontend/src/features/tabs/UnifiedCanvas/UnifiedCanvasPanel.tsx similarity index 98% rename from frontend/src/features/tabs/Outpainting/OutpaintingPanel.tsx rename to frontend/src/features/tabs/UnifiedCanvas/UnifiedCanvasPanel.tsx index 5428a923f2..d5ca42137f 100644 --- a/frontend/src/features/tabs/Outpainting/OutpaintingPanel.tsx +++ b/frontend/src/features/tabs/UnifiedCanvas/UnifiedCanvasPanel.tsx @@ -18,7 +18,7 @@ import ProcessButtons from 'features/options/ProcessButtons/ProcessButtons'; import PromptInput from 'features/options/PromptInput/PromptInput'; import InvokeOptionsPanel from 'features/tabs/InvokeOptionsPanel'; -export default function OutpaintingPanel() { +export default function UnifiedCanvasPanel() { const showAdvancedOptions = useAppSelector( (state: RootState) => state.options.showAdvancedOptions ); diff --git a/frontend/src/features/tabs/Inpainting/InpaintingWorkarea.tsx b/frontend/src/features/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx similarity index 65% rename from frontend/src/features/tabs/Inpainting/InpaintingWorkarea.tsx rename to frontend/src/features/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx index cb9dba07d5..0e03a3b118 100644 --- a/frontend/src/features/tabs/Inpainting/InpaintingWorkarea.tsx +++ b/frontend/src/features/tabs/UnifiedCanvas/UnifiedCanvasWorkarea.tsx @@ -1,21 +1,21 @@ -import InpaintingPanel from './InpaintingPanel'; -import InpaintingDisplay from './InpaintingDisplay'; +import UnifiedCanvasPanel from './UnifiedCanvasPanel'; +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'; -export default function InpaintingWorkarea() { +export default function UnifiedCanvasWorkarea() { const dispatch = useAppDispatch(); useEffect(() => { dispatch(setDoesCanvasNeedScaling(true)); }, [dispatch]); return ( } + optionsPanel={} styleClass="inpainting-workarea-overrides" > - + ); }