Fixes app after removing in/out-painting refs

This commit is contained in:
psychedelicious 2022-11-17 12:42:47 +11:00 committed by blessedcoolant
parent 98e3bbb3bd
commit 223e0529ba
35 changed files with 170 additions and 509 deletions

View File

@ -616,10 +616,7 @@ class InvokeAIWebServer:
""" """
Prepare for generation based on generation_mode Prepare for generation based on generation_mode
""" """
if generation_parameters["generation_mode"] in [ if generation_parameters["generation_mode"] == "unifiedCanvas":
"outpainting",
"inpainting",
]:
""" """
generation_parameters["init_img"] is a base64 image generation_parameters["init_img"] is a base64 image
generation_parameters["init_mask"] 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() original_bounding_box = generation_parameters["bounding_box"].copy()
if generation_parameters["generation_mode"] == "outpainting": initial_image = dataURL_to_image(
initial_image = dataURL_to_image( generation_parameters["init_img"]
generation_parameters["init_img"] ).convert("RGBA")
).convert("RGBA")
""" """
The outpaint image and mask are pre-cropped by the UI, so the bounding box we pass The outpaint image and mask are pre-cropped by the UI, so the bounding box we pass
to the generator should be: to the generator should be:
{ {
"x": 0, "x": 0,
"y": 0, "y": 0,
"width": original_bounding_box["width"], "width": original_bounding_box["width"],
"height": original_bounding_box["height"] "height": original_bounding_box["height"]
} }
""" """
generation_parameters["bounding_box"]["x"] = 0 generation_parameters["bounding_box"]["x"] = 0
generation_parameters["bounding_box"]["y"] = 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"]
)
# Convert mask dataURL to an image and convert to greyscale # Convert mask dataURL to an image and convert to greyscale
mask_image = dataURL_to_image( mask_image = dataURL_to_image(
@ -930,7 +915,7 @@ class InvokeAIWebServer:
if "init_mask" in all_parameters: if "init_mask" in all_parameters:
all_parameters["init_mask"] = "" # TODO: store the mask in metadata 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 all_parameters["bounding_box"] = original_bounding_box
metadata = self.parameters_to_generated_image_metadata(all_parameters) metadata = self.parameters_to_generated_image_metadata(all_parameters)

View File

@ -50,7 +50,7 @@ const appSelector = createSelector(
const shouldShowGalleryButton = const shouldShowGalleryButton =
!(shouldShowGallery || (shouldHoldGalleryOpen && !shouldPinGallery)) && !(shouldShowGallery || (shouldHoldGalleryOpen && !shouldPinGallery)) &&
['txt2img', 'img2img', 'inpainting', 'outpainting'].includes( ['txt2img', 'img2img', 'unifiedCanvas'].includes(
activeTabName activeTabName
); );
@ -59,7 +59,7 @@ const appSelector = createSelector(
shouldShowOptionsPanel || shouldShowOptionsPanel ||
(shouldHoldOptionsPanelOpen && !shouldPinOptionsPanel) (shouldHoldOptionsPanelOpen && !shouldPinOptionsPanel)
) && ) &&
['txt2img', 'img2img', 'inpainting', 'outpainting'].includes( ['txt2img', 'img2img', 'unifiedCanvas'].includes(
activeTabName activeTabName
); );

View File

@ -44,11 +44,6 @@ export const readinessSelector = createSelector(
reasonsWhyNotReady.push('No initial image selected'); reasonsWhyNotReady.push('No initial image selected');
} }
if (activeTabName === 'inpainting' && !initialCanvasImage) {
isReady = false;
reasonsWhyNotReady.push('No inpainting image selected');
}
// TODO: job queue // TODO: job queue
// Cannot generate if already processing an image // Cannot generate if already processing an image
if (isProcessing) { if (isProcessing) {

View File

@ -54,24 +54,26 @@ const makeSocketIOEmitters = (
systemState, systemState,
}; };
if (generationMode === 'inpainting') { // if (generationMode === 'inpainting') {
const initialCanvasImage = initialCanvasImageSelector(getState()); // const initialCanvasImage = initialCanvasImageSelector(getState());
const imageUrl = initialCanvasImage?.image.url; // const imageUrl = initialCanvasImage?.image.url;
if (!imageUrl) { // if (!imageUrl) {
dispatch( // dispatch(
addLogEntry({ // addLogEntry({
timestamp: dateFormat(new Date(), 'isoDateTime'), // timestamp: dateFormat(new Date(), 'isoDateTime'),
message: 'Inpainting image not loaded, cannot generate image.', // message: 'Inpainting image not loaded, cannot generate image.',
level: 'error', // level: 'error',
}) // })
); // );
dispatch(errorOccurred()); // dispatch(errorOccurred());
return; // return;
} // }
frontendToBackendParametersConfig.imageToProcessUrl = imageUrl; // frontendToBackendParametersConfig.imageToProcessUrl = imageUrl;
} else if (!['txt2img', 'img2img'].includes(generationMode)) { // } else
if (!['txt2img', 'img2img'].includes(generationMode)) {
if (!galleryState.currentImage?.url) return; if (!galleryState.currentImage?.url) return;
frontendToBackendParametersConfig.imageToProcessUrl = frontendToBackendParametersConfig.imageToProcessUrl =

View File

@ -37,10 +37,7 @@ import {
requestNewImages, requestNewImages,
requestSystemConfig, requestSystemConfig,
} from './actions'; } from './actions';
import { import { addImageToStagingArea } from 'features/canvas/canvasSlice';
addImageToStagingArea,
setImageToInpaint,
} from 'features/canvas/canvasSlice';
import { tabMap } from 'features/tabs/InvokeTabs'; import { tabMap } from 'features/tabs/InvokeTabs';
/** /**
@ -120,23 +117,15 @@ const makeSocketIOListeners = (
); );
} }
if ( if (generationMode === 'unifiedCanvas' && data.boundingBox) {
['inpainting', 'outpainting'].includes(generationMode) &&
data.boundingBox
) {
newImage.category = 'temp'; newImage.category = 'temp';
const { boundingBox } = data; const { boundingBox } = data;
dispatch(
if (generationMode === 'inpainting') { addImageToStagingArea({
dispatch(setImageToInpaint(newImage)); image: newImage,
} else { boundingBox,
dispatch( })
addImageToStagingArea({ );
image: newImage,
boundingBox,
})
);
}
} }
if (shouldLoopback) { if (shouldLoopback) {
@ -146,10 +135,6 @@ const makeSocketIOListeners = (
dispatch(setInitialImage(newImage)); dispatch(setInitialImage(newImage));
break; break;
} }
case 'inpainting': {
dispatch(setImageToInpaint(newImage));
break;
}
} }
} }

View File

@ -28,14 +28,8 @@ import { socketioMiddleware } from './socketio/middleware';
* The necesssary nested persistors with blacklists are configured below. * The necesssary nested persistors with blacklists are configured below.
*/ */
const genericCanvasBlacklist = ['cursorPosition']; const canvasBlacklist = ['cursorPosition'].map(
(blacklistItem) => `canvas.${blacklistItem}`
const inpaintingCanvasBlacklist = genericCanvasBlacklist.map(
(blacklistItem) => `canvas.inpainting.${blacklistItem}`
);
const outpaintingCanvasBlacklist = genericCanvasBlacklist.map(
(blacklistItem) => `canvas.outpainting.${blacklistItem}`
); );
const systemBlacklist = [ const systemBlacklist = [
@ -73,12 +67,7 @@ const rootPersistConfig = getPersistConfig({
key: 'root', key: 'root',
storage, storage,
rootReducer, rootReducer,
blacklist: [ blacklist: [...canvasBlacklist, ...systemBlacklist, ...galleryBlacklist],
...inpaintingCanvasBlacklist,
...outpaintingCanvasBlacklist,
...systemBlacklist,
...galleryBlacklist,
],
debounce: 300, debounce: 300,
}); });

View File

@ -135,7 +135,7 @@ const ImageUploader = (props: ImageUploaderProps) => {
}; };
}, [dispatch, toast, activeTabName]); }, [dispatch, toast, activeTabName]);
const overlaySecondaryText = ['img2img', 'inpainting'].includes(activeTabName) const overlaySecondaryText = ['img2img', 'unifiedCanvas'].includes(activeTabName)
? ` to ${tabDict[activeTabName as keyof typeof tabDict].tooltip}` ? ` to ${tabDict[activeTabName as keyof typeof tabDict].tooltip}`
: ``; : ``;

Binary file not shown.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.8 KiB

View File

@ -106,10 +106,7 @@ export const frontendToBackendParameters = (
} }
// inpainting exclusive parameters // inpainting exclusive parameters
if ( if (generationMode === 'unifiedCanvas' && canvasImageLayerRef.current) {
['inpainting', 'outpainting'].includes(generationMode) &&
canvasImageLayerRef.current
) {
const { const {
layerState: { objects }, layerState: { objects },
boundingBoxCoordinates, boundingBoxCoordinates,
@ -146,44 +143,42 @@ export const frontendToBackendParameters = (
generationParameters.bounding_box = boundingBox; generationParameters.bounding_box = boundingBox;
if (generationMode === 'outpainting') { const tempScale = canvasImageLayerRef.current.scale();
const tempScale = canvasImageLayerRef.current.scale();
canvasImageLayerRef.current.scale({ canvasImageLayerRef.current.scale({
x: 1 / stageScale, x: 1 / stageScale,
y: 1 / stageScale, y: 1 / stageScale,
}); });
const absPos = canvasImageLayerRef.current.getAbsolutePosition(); const absPos = canvasImageLayerRef.current.getAbsolutePosition();
const imageDataURL = canvasImageLayerRef.current.toDataURL({ const imageDataURL = canvasImageLayerRef.current.toDataURL({
x: boundingBox.x + absPos.x, x: boundingBox.x + absPos.x,
y: boundingBox.y + absPos.y, y: boundingBox.y + absPos.y,
width: boundingBox.width, width: boundingBox.width,
height: boundingBox.height, height: boundingBox.height,
}); });
if (enableImageDebugging) { if (enableImageDebugging) {
openBase64ImageInTab([ openBase64ImageInTab([
{ base64: maskDataURL, caption: 'mask sent as init_mask' }, { base64: maskDataURL, caption: 'mask sent as init_mask' },
{ base64: imageDataURL, caption: 'image sent as init_img' }, { 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;
} }
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) { if (shouldGenerateVariations) {

View File

@ -93,7 +93,6 @@ const selector = createSelector(
stageDimensions, stageDimensions,
stageScale, stageScale,
tool, tool,
isOnOutpaintingTab: activeTabName === 'outpainting',
isStaging, isStaging,
shouldShowIntermediates, shouldShowIntermediates,
shouldLockToInitialImage, shouldLockToInitialImage,
@ -122,7 +121,6 @@ const IAICanvas = () => {
stageDimensions, stageDimensions,
stageScale, stageScale,
tool, tool,
isOnOutpaintingTab,
isStaging, isStaging,
shouldShowIntermediates, shouldShowIntermediates,
shouldLockToInitialImage, shouldLockToInitialImage,
@ -207,11 +205,7 @@ const IAICanvas = () => {
onDragEnd={handleDragEnd} onDragEnd={handleDragEnd}
onWheel={handleWheel} onWheel={handleWheel}
listening={(tool === 'move' || isStaging) && !isModifyingBoundingBox} listening={(tool === 'move' || isStaging) && !isModifyingBoundingBox}
draggable={ draggable={(tool === 'move' || isStaging) && !isModifyingBoundingBox}
(tool === 'move' || isStaging) &&
!isModifyingBoundingBox &&
isOnOutpaintingTab
}
> >
<Layer id={'grid'} visible={shouldShowGrid}> <Layer id={'grid'} visible={shouldShowGrid}>
<IAICanvasGrid /> <IAICanvasGrid />
@ -243,7 +237,7 @@ const IAICanvas = () => {
/> />
</Layer> </Layer>
</Stage> </Stage>
{isOnOutpaintingTab && <IAICanvasStatusText />} <IAICanvasStatusText />
<IAICanvasStagingAreaToolbar /> <IAICanvasStagingAreaToolbar />
</div> </div>
</div> </div>

View File

@ -18,7 +18,7 @@ import {
import _ from 'lodash'; import _ from 'lodash';
import IAICanvasMaskColorPicker from './IAICanvasMaskControls/IAICanvasMaskColorPicker'; import IAICanvasMaskColorPicker from './IAICanvasMaskControls/IAICanvasMaskColorPicker';
const inpaintingBrushSelector = createSelector( const selector = createSelector(
[canvasSelector, activeTabNameSelector], [canvasSelector, activeTabNameSelector],
(canvas, activeTabName) => { (canvas, activeTabName) => {
const { tool, brushSize } = canvas; const { tool, brushSize } = canvas;
@ -38,9 +38,7 @@ const inpaintingBrushSelector = createSelector(
export default function IAICanvasBrushControl() { export default function IAICanvasBrushControl() {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { tool, brushSize, activeTabName } = useAppSelector( const { tool, brushSize, activeTabName } = useAppSelector(selector);
inpaintingBrushSelector
);
const handleSelectBrushTool = () => dispatch(setTool('brush')); const handleSelectBrushTool = () => dispatch(setTool('brush'));

View File

@ -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 (
<IAIIconButton
aria-label="Clear Image"
tooltip="Clear Image"
icon={<FaTrash size={16} />}
onClick={handleClearImage}
/>
);
}

View File

@ -40,7 +40,7 @@ export default function IAICanvasMaskVisibilityControl() {
handleToggleShouldShowMask(); handleToggleShouldShowMask();
}, },
{ {
enabled: activeTabName === 'inpainting' || activeTabName == 'outpainting', enabled: activeTabName === 'unifiedCanvas',
}, },
[activeTabName, isMaskEnabled] [activeTabName, isMaskEnabled]
); );

View File

@ -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 (
// <Group {...rest} globalCompositeOperation={'destination-out'}>
// {eraserLines.map((line, i) => (
// <Line
// key={i}
// points={line.points}
// stroke={'rgb(0,0,0)'} // The lines can be any color, just need alpha > 0
// strokeWidth={line.strokeWidth * 2}
// tension={0}
// lineCap="round"
// lineJoin="round"
// shadowForStrokeEnabled={false}
// listening={false}
// globalCompositeOperation={'source-over'}
// />
// ))}
// </Group>
// );
// };
// export default IAICanvasEraserLines;
export default {}

View File

@ -52,9 +52,6 @@ const IAICanvasResizer = () => {
if (!initialCanvasImage?.image) return; if (!initialCanvasImage?.image) return;
const { width: imageWidth, height: imageHeight } =
initialCanvasImage.image;
dispatch( dispatch(
setCanvasContainerDimensions({ setCanvasContainerDimensions({
width: clientWidth, width: clientWidth,
@ -69,34 +66,6 @@ const IAICanvasResizer = () => {
} }
dispatch(setDoesCanvasNeedScaling(false)); 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); }, 0);
}, [ }, [
dispatch, dispatch,
@ -104,6 +73,7 @@ const IAICanvasResizer = () => {
doesCanvasNeedScaling, doesCanvasNeedScaling,
activeTabName, activeTabName,
isCanvasInitialized, isCanvasInitialized,
shouldLockToInitialImage,
]); ]);
return ( return (

View File

@ -6,7 +6,7 @@ import {
} from 'common/util/roundDownToMultiple'; } from 'common/util/roundDownToMultiple';
import _ from 'lodash'; import _ from 'lodash';
export const setInitialCanvasImage = ( export const setInitialCanvasImage_reducer = (
state: CanvasState, state: CanvasState,
image: InvokeAI.Image image: InvokeAI.Image
) => { ) => {

View File

@ -8,13 +8,11 @@ import { roundDownToMultiple } from 'common/util/roundDownToMultiple';
import { RootState } from 'app/store'; import { RootState } from 'app/store';
import { mergeAndUploadCanvas } from './util/mergeAndUploadCanvas'; import { mergeAndUploadCanvas } from './util/mergeAndUploadCanvas';
import { uploadImage } from 'features/gallery/util/uploadImage'; import { uploadImage } from 'features/gallery/util/uploadImage';
import { setInitialCanvasImage } from './canvasReducers'; import { setInitialCanvasImage_reducer } from './canvasReducers';
import calculateScale from './util/calculateScale'; import calculateScale from './util/calculateScale';
import calculateCoordinates from './util/calculateCoordinates'; import calculateCoordinates from './util/calculateCoordinates';
import floorCoordinates from './util/floorCoordinates'; import floorCoordinates from './util/floorCoordinates';
export type CanvasMode = 'inpainting' | 'outpainting';
export type CanvasLayer = 'base' | 'mask'; export type CanvasLayer = 'base' | 'mask';
export type CanvasDrawingTool = 'brush' | 'eraser'; export type CanvasDrawingTool = 'brush' | 'eraser';
@ -256,15 +254,8 @@ export const canvasSlice = createSlice({
setCursorPosition: (state, action: PayloadAction<Vector2d | null>) => { setCursorPosition: (state, action: PayloadAction<Vector2d | null>) => {
state.cursorPosition = action.payload; state.cursorPosition = action.payload;
}, },
clearImageToInpaint: (state) => { setInitialCanvasImage: (state, action: PayloadAction<InvokeAI.Image>) => {
// TODO setInitialCanvasImage_reducer(state, action.payload);
// state.inpainting.imageToInpaint = undefined;
},
setImageToOutpaint: (state, action: PayloadAction<InvokeAI.Image>) => {
setInitialCanvasImage(state, action.payload);
},
setImageToInpaint: (state, action: PayloadAction<InvokeAI.Image>) => {
setInitialCanvasImage(state, action.payload);
}, },
setStageDimensions: (state, action: PayloadAction<Dimensions>) => { setStageDimensions: (state, action: PayloadAction<Dimensions>) => {
state.stageDimensions = action.payload; state.stageDimensions = action.payload;
@ -642,10 +633,8 @@ export const canvasSlice = createSlice({
if (kind !== 'init') return; if (kind !== 'init') return;
if (activeTabName === 'inpainting') { if (activeTabName === 'unifiedCanvas') {
setInitialCanvasImage(state, image); setInitialCanvasImage_reducer(state, image);
} else if (activeTabName === 'outpainting') {
setInitialCanvasImage(state, image);
} }
}); });
}, },
@ -665,13 +654,11 @@ export const {
setShouldShowBrushPreview, setShouldShowBrushPreview,
setMaskColor, setMaskColor,
clearMask, clearMask,
clearImageToInpaint,
undo, undo,
redo, redo,
setCursorPosition, setCursorPosition,
setStageDimensions, setStageDimensions,
setImageToInpaint, setInitialCanvasImage,
setImageToOutpaint,
setBoundingBoxDimensions, setBoundingBoxDimensions,
setBoundingBoxCoordinates, setBoundingBoxCoordinates,
setBoundingBoxPreviewFill, setBoundingBoxPreviewFill,

View File

@ -13,7 +13,7 @@ import { canvasSelector } from '../canvasSlice';
import { useRef } from 'react'; import { useRef } from 'react';
import { stageRef } from '../IAICanvas'; import { stageRef } from '../IAICanvas';
const inpaintingCanvasHotkeysSelector = createSelector( const selector = createSelector(
[canvasSelector, activeTabNameSelector], [canvasSelector, activeTabNameSelector],
(canvas, activeTabName) => { (canvas, activeTabName) => {
const { const {
@ -40,9 +40,8 @@ const inpaintingCanvasHotkeysSelector = createSelector(
const useInpaintingCanvasHotkeys = () => { const useInpaintingCanvasHotkeys = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { activeTabName, shouldShowBoundingBox, tool } = useAppSelector( const { activeTabName, shouldShowBoundingBox, tool } =
inpaintingCanvasHotkeysSelector useAppSelector(selector);
);
const previousToolRef = useRef<CanvasTool | null>(null); const previousToolRef = useRef<CanvasTool | null>(null);
// Toggle lock bounding box // Toggle lock bounding box

View File

@ -60,12 +60,7 @@ const useCanvasWheel = (stageRef: MutableRefObject<Konva.Stage | null>) => {
return useCallback( return useCallback(
(e: KonvaEventObject<WheelEvent>) => { (e: KonvaEventObject<WheelEvent>) => {
// stop default scrolling // stop default scrolling
if ( if (!stageRef.current || isMoveStageKeyHeld || !initialCanvasImage)
activeTabName !== 'outpainting' ||
!stageRef.current ||
isMoveStageKeyHeld ||
!initialCanvasImage
)
return; return;
e.evt.preventDefault(); e.evt.preventDefault();

View File

@ -36,9 +36,9 @@ import {
FaTrash, FaTrash,
} from 'react-icons/fa'; } from 'react-icons/fa';
import { import {
setImageToInpaint,
setDoesCanvasNeedScaling, setDoesCanvasNeedScaling,
setImageToOutpaint, setInitialCanvasImage,
setShouldLockToInitialImage,
} from 'features/canvas/canvasSlice'; } from 'features/canvas/canvasSlice';
import { GalleryState } from './gallerySlice'; import { GalleryState } from './gallerySlice';
import { activeTabNameSelector } from 'features/options/optionsSelectors'; import { activeTabNameSelector } from 'features/options/optionsSelectors';
@ -320,12 +320,12 @@ const CurrentImageButtons = () => {
if (!currentImage) return; if (!currentImage) return;
if (isLightBoxOpen) dispatch(setIsLightBoxOpen(false)); if (isLightBoxOpen) dispatch(setIsLightBoxOpen(false));
dispatch(setImageToInpaint(currentImage)); dispatch(setInitialCanvasImage(currentImage));
dispatch(setActiveTab('inpainting')); dispatch(setShouldLockToInitialImage(true));
dispatch(setDoesCanvasNeedScaling(true)); dispatch(setDoesCanvasNeedScaling(true));
toast({ toast({
title: 'Sent to Inpainting', title: 'Sent to Unified Canvas',
status: 'success', status: 'success',
duration: 2500, duration: 2500,
isClosable: true, isClosable: true,
@ -336,12 +336,12 @@ const CurrentImageButtons = () => {
if (!currentImage) return; if (!currentImage) return;
if (isLightBoxOpen) dispatch(setIsLightBoxOpen(false)); if (isLightBoxOpen) dispatch(setIsLightBoxOpen(false));
dispatch(setImageToOutpaint(currentImage)); dispatch(setInitialCanvasImage(currentImage));
dispatch(setActiveTab('outpainting')); dispatch(setShouldLockToInitialImage(false));
dispatch(setDoesCanvasNeedScaling(true)); dispatch(setDoesCanvasNeedScaling(true));
toast({ toast({
title: 'Sent to Inpainting', title: 'Sent to Unified Canvas',
status: 'success', status: 'success',
duration: 2500, duration: 2500,
isClosable: true, isClosable: true,

View File

@ -23,8 +23,9 @@ import {
import * as InvokeAI from 'app/invokeai'; import * as InvokeAI from 'app/invokeai';
import * as ContextMenu from '@radix-ui/react-context-menu'; import * as ContextMenu from '@radix-ui/react-context-menu';
import { import {
setImageToInpaint, setDoesCanvasNeedScaling,
setImageToOutpaint, setInitialCanvasImage,
setShouldLockToInitialImage,
} from 'features/canvas/canvasSlice'; } from 'features/canvas/canvasSlice';
import { hoverableImageSelector } from './gallerySliceSelectors'; import { hoverableImageSelector } from './gallerySliceSelectors';
@ -97,10 +98,15 @@ const HoverableImage = memo((props: HoverableImageProps) => {
const handleSendToInpainting = () => { const handleSendToInpainting = () => {
if (isLightBoxOpen) dispatch(setIsLightBoxOpen(false)); if (isLightBoxOpen) dispatch(setIsLightBoxOpen(false));
dispatch(setImageToInpaint(image));
if (activeTabName !== 'inpainting') { dispatch(setInitialCanvasImage(image));
dispatch(setActiveTab('inpainting')); dispatch(setShouldLockToInitialImage(true));
dispatch(setDoesCanvasNeedScaling(true));
if (activeTabName !== 'unifiedCanvas') {
dispatch(setActiveTab('unifiedCanvas'));
} }
toast({ toast({
title: 'Sent to Inpainting', title: 'Sent to Inpainting',
status: 'success', status: 'success',
@ -111,10 +117,15 @@ const HoverableImage = memo((props: HoverableImageProps) => {
const handleSendToOutpainting = () => { const handleSendToOutpainting = () => {
if (isLightBoxOpen) dispatch(setIsLightBoxOpen(false)); if (isLightBoxOpen) dispatch(setIsLightBoxOpen(false));
dispatch(setImageToOutpaint(image));
if (activeTabName !== 'outpainting') { dispatch(setInitialCanvasImage(image));
dispatch(setActiveTab('outpainting')); dispatch(setShouldLockToInitialImage(true));
dispatch(setDoesCanvasNeedScaling(true));
if (activeTabName !== 'unifiedCanvas') {
dispatch(setActiveTab('unifiedCanvas'));
} }
toast({ toast({
title: 'Sent to Outpainting', title: 'Sent to Outpainting',
status: 'success', status: 'success',

View File

@ -76,7 +76,7 @@ export default function ImageGallery() {
return; return;
} }
if (activeTabName === 'inpainting' || activeTabName === 'outpainting') { if (activeTabName === 'unifiedCanvas') {
dispatch(setGalleryWidth(190)); dispatch(setGalleryWidth(190));
setGalleryMinWidth(190); setGalleryMinWidth(190);
setGalleryMaxWidth(190); setGalleryMaxWidth(190);

View File

@ -15,7 +15,7 @@ export default function MainHeight() {
return ( return (
<IAISelect <IAISelect
isDisabled={activeTabName === 'inpainting'} isDisabled={activeTabName === 'unifiedCanvas'}
label="Height" label="Height"
value={height} value={height}
flexGrow={1} flexGrow={1}

View File

@ -16,7 +16,7 @@ export default function MainWidth() {
return ( return (
<IAISelect <IAISelect
isDisabled={activeTabName === 'inpainting'} isDisabled={activeTabName === 'unifiedCanvas'}
label="Width" label="Width"
value={width} value={width}
flexGrow={1} flexGrow={1}

View File

@ -142,7 +142,7 @@ export default function HotkeysModal({ children }: HotkeysModalProps) {
}, },
]; ];
const inpaintingHotkeys = [ const unifiedCanvasHotkeys = [
{ {
title: 'Select Brush', title: 'Select Brush',
desc: 'Selects the inpainting brush', desc: 'Selects the inpainting brush',
@ -223,9 +223,6 @@ export default function HotkeysModal({ children }: HotkeysModalProps) {
desc: 'Expand your inpainting work area', desc: 'Expand your inpainting work area',
hotkey: 'Shift+J', hotkey: 'Shift+J',
}, },
];
const outpaintingHotkeys = [
{ {
title: 'Erase Canvas', title: 'Erase Canvas',
desc: 'Erase the images on the canvas', desc: 'Erase the images on the canvas',
@ -301,17 +298,7 @@ export default function HotkeysModal({ children }: HotkeysModalProps) {
<AccordionIcon /> <AccordionIcon />
</AccordionButton> </AccordionButton>
<AccordionPanel> <AccordionPanel>
{renderHotkeyModalItems(inpaintingHotkeys)} {renderHotkeyModalItems(unifiedCanvasHotkeys)}
</AccordionPanel>
</AccordionItem>
<AccordionItem>
<AccordionButton className="hotkeys-modal-button">
<h2>Outpainting Hotkeys</h2>
<AccordionIcon />
</AccordionButton>
<AccordionPanel>
{renderHotkeyModalItems(outpaintingHotkeys)}
</AccordionPanel> </AccordionPanel>
</AccordionItem> </AccordionItem>
</Accordion> </Accordion>

View File

@ -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 ? (
<div className="inpainting-main-area">
<IAICanvasControls />
<div className="inpainting-canvas-area">
{doesCanvasNeedScaling ? <IAICanvasResizer /> : <IAICanvas />}
</div>
</div>
) : (
<ImageUploadButton />
);
return (
<div
className={
showDualDisplay ? 'workarea-split-view' : 'workarea-single-view'
}
>
<div className="workarea-split-view-left">{inpaintingComponent}</div>
{showDualDisplay && (
<div className="workarea-split-view-right">
<CurrentImageDisplay />
</div>
)}
</div>
);
};
export default InpaintingDisplay;

View File

@ -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: <SeedHeader />,
feature: Feature.SEED,
options: <SeedOptions />,
},
variations: {
header: <VariationsHeader />,
feature: Feature.VARIATIONS,
options: <VariationsOptions />,
},
face_restore: {
header: <FaceRestoreHeader />,
feature: Feature.FACE_CORRECTION,
options: <FaceRestoreOptions />,
},
upscale: {
header: <UpscaleHeader />,
feature: Feature.UPSCALE,
options: <UpscaleOptions />,
},
};
return (
<InvokeOptionsPanel>
<PromptInput />
<ProcessButtons />
<MainOptions />
<ImageToImageStrength
label="Image To Image Strength"
styleClass="main-option-block image-to-image-strength-main-option"
/>
<InpaintingSettings />
<MainAdvancedOptionsCheckbox />
{showAdvancedOptions && (
<OptionsAccordion accordionInfo={imageToImageAccordions} />
)}
</InvokeOptionsPanel>
);
}

View File

@ -17,12 +17,12 @@ import {
setShouldShowOptionsPanel, setShouldShowOptionsPanel,
} from 'features/options/optionsSlice'; } from 'features/options/optionsSlice';
import ImageToImageWorkarea from './ImageToImage'; import ImageToImageWorkarea from './ImageToImage';
import InpaintingWorkarea from './Inpainting/InpaintingWorkarea';
import TextToImageWorkarea from './TextToImage'; import TextToImageWorkarea from './TextToImage';
import Lightbox from 'features/lightbox/Lightbox'; import Lightbox from 'features/lightbox/Lightbox';
import { setDoesCanvasNeedScaling } from 'features/canvas/canvasSlice'; import { setDoesCanvasNeedScaling } from 'features/canvas/canvasSlice';
import OutpaintingWorkarea from './Outpainting/OutpaintingWorkarea'; import UnifiedCanvasWorkarea from './UnifiedCanvas/UnifiedCanvasWorkarea';
import { setShouldShowGallery } from 'features/gallery/gallerySlice'; import { setShouldShowGallery } from 'features/gallery/gallerySlice';
import UnifiedCanvasIcon from 'common/icons/UnifiedCanvasIcon';
export const tabDict = { export const tabDict = {
txt2img: { txt2img: {
@ -35,15 +35,10 @@ export const tabDict = {
workarea: <ImageToImageWorkarea />, workarea: <ImageToImageWorkarea />,
tooltip: 'Image To Image', tooltip: 'Image To Image',
}, },
inpainting: { unifiedCanvas: {
title: <InpaintIcon fill={'black'} boxSize={'2.5rem'} />, title: <UnifiedCanvasIcon fill={'black'} boxSize={'2.5rem'} />,
workarea: <InpaintingWorkarea />, workarea: <UnifiedCanvasWorkarea />,
tooltip: 'Inpainting', tooltip: 'Unified Canvas',
},
outpainting: {
title: <OutpaintIcon fill={'black'} boxSize={'2.5rem'} />,
workarea: <OutpaintingWorkarea />,
tooltip: 'Outpainting',
}, },
nodes: { nodes: {
title: <NodesIcon fill={'black'} boxSize={'2.5rem'} />, title: <NodesIcon fill={'black'} boxSize={'2.5rem'} />,

View File

@ -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 (
<InvokeWorkarea
optionsPanel={<OutpaintingPanel />}
styleClass="inpainting-workarea-overrides"
>
<OutpaintingDisplay />
</InvokeWorkarea>
);
}

View File

@ -12,7 +12,7 @@ import {
import IAICanvas from 'features/canvas/IAICanvas'; import IAICanvas from 'features/canvas/IAICanvas';
import IAICanvasOutpaintingControls from 'features/canvas/IAICanvasOutpaintingControls'; import IAICanvasOutpaintingControls from 'features/canvas/IAICanvasOutpaintingControls';
const outpaintingDisplaySelector = createSelector( const selector = createSelector(
[canvasSelector], [canvasSelector],
(canvas) => { (canvas) => {
const { const {
@ -31,11 +31,10 @@ const outpaintingDisplaySelector = createSelector(
} }
); );
const OutpaintingDisplay = () => { const UnifiedCanvasDisplay = () => {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
const { doesCanvasNeedScaling, doesOutpaintingHaveObjects } = useAppSelector( const { doesCanvasNeedScaling, doesOutpaintingHaveObjects } =
outpaintingDisplaySelector useAppSelector(selector);
);
useLayoutEffect(() => { useLayoutEffect(() => {
const resizeCallback = _.debounce( const resizeCallback = _.debounce(
@ -46,17 +45,6 @@ const OutpaintingDisplay = () => {
return () => window.removeEventListener('resize', resizeCallback); return () => window.removeEventListener('resize', resizeCallback);
}, [dispatch]); }, [dispatch]);
const outpaintingComponent = doesOutpaintingHaveObjects ? (
<div className="inpainting-main-area">
<IAICanvasOutpaintingControls />
<div className="inpainting-canvas-area">
{doesCanvasNeedScaling ? <IAICanvasResizer /> : <IAICanvas />}
</div>
</div>
) : (
<ImageUploadButton />
);
return ( return (
<div className={'workarea-single-view'}> <div className={'workarea-single-view'}>
<div className="workarea-split-view-left"> <div className="workarea-split-view-left">
@ -67,9 +55,8 @@ const OutpaintingDisplay = () => {
</div> </div>
</div> </div>
</div> </div>
{/* <div className="workarea-split-view-left">{outpaintingComponent}</div> */}
</div> </div>
); );
}; };
export default OutpaintingDisplay; export default UnifiedCanvasDisplay;

View File

@ -18,7 +18,7 @@ import ProcessButtons from 'features/options/ProcessButtons/ProcessButtons';
import PromptInput from 'features/options/PromptInput/PromptInput'; import PromptInput from 'features/options/PromptInput/PromptInput';
import InvokeOptionsPanel from 'features/tabs/InvokeOptionsPanel'; import InvokeOptionsPanel from 'features/tabs/InvokeOptionsPanel';
export default function OutpaintingPanel() { export default function UnifiedCanvasPanel() {
const showAdvancedOptions = useAppSelector( const showAdvancedOptions = useAppSelector(
(state: RootState) => state.options.showAdvancedOptions (state: RootState) => state.options.showAdvancedOptions
); );

View File

@ -1,21 +1,21 @@
import InpaintingPanel from './InpaintingPanel'; import UnifiedCanvasPanel from './UnifiedCanvasPanel';
import InpaintingDisplay from './InpaintingDisplay'; import UnifiedCanvasDisplay from './UnifiedCanvasDisplay';
import InvokeWorkarea from 'features/tabs/InvokeWorkarea'; import InvokeWorkarea from 'features/tabs/InvokeWorkarea';
import { useAppDispatch } from 'app/store'; import { useAppDispatch } from 'app/store';
import { useEffect } from 'react'; import { useEffect } from 'react';
import { setDoesCanvasNeedScaling } from 'features/canvas/canvasSlice'; import { setDoesCanvasNeedScaling } from 'features/canvas/canvasSlice';
export default function InpaintingWorkarea() { export default function UnifiedCanvasWorkarea() {
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
useEffect(() => { useEffect(() => {
dispatch(setDoesCanvasNeedScaling(true)); dispatch(setDoesCanvasNeedScaling(true));
}, [dispatch]); }, [dispatch]);
return ( return (
<InvokeWorkarea <InvokeWorkarea
optionsPanel={<InpaintingPanel />} optionsPanel={<UnifiedCanvasPanel />}
styleClass="inpainting-workarea-overrides" styleClass="inpainting-workarea-overrides"
> >
<InpaintingDisplay /> <UnifiedCanvasDisplay />
</InvokeWorkarea> </InvokeWorkarea>
); );
} }