Add Inpainting Settings

- Enable and Disable Inpainting Box (with backend support)
- Enable and Disable Bounding Box Darkening
- Reset Bounding Box
This commit is contained in:
blessedcoolant 2022-10-28 02:26:28 +13:00 committed by psychedelicious
parent 3ce60161d2
commit 5cae8206f9
12 changed files with 315 additions and 126 deletions

View File

@ -101,13 +101,26 @@ export const frontendToBackendParameters = (
lines, lines,
boundingBoxCoordinate: { x, y }, boundingBoxCoordinate: { x, y },
boundingBoxDimensions: { width, height }, boundingBoxDimensions: { width, height },
shouldShowBoundingBox,
} = inpaintingState; } = inpaintingState;
let bx = x,
by = y,
bwidth = width,
bheight = height;
if (!shouldShowBoundingBox) {
bx = 0;
by = 0;
bwidth = maskImageElement.width;
bheight = maskImageElement.height;
}
const boundingBox = { const boundingBox = {
x, x: bx,
y, y: by,
width, width: bwidth,
height, height: bheight,
}; };
generationParameters.init_img = imageToProcessUrl; generationParameters.init_img = imageToProcessUrl;

View File

@ -1,13 +0,0 @@
.inpainting-bounding-box-dimensions {
display: flex;
flex-direction: column;
row-gap: 1rem;
max-width: 100%;
}
.inpainting-bounding-box-dimensions-slider-numberinput {
display: flex;
flex-direction: row;
column-gap: 0.5rem;
width: 100%;
}

View File

@ -1,89 +0,0 @@
import { FormLabel } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import _ from 'lodash';
import {
RootState,
useAppDispatch,
useAppSelector,
} from '../../../../app/store';
import IAINumberInput from '../../../../common/components/IAINumberInput';
import IAISlider from '../../../../common/components/IAISlider';
import { roundDownToMultiple } from '../../../../common/util/roundDownToMultiple';
import {
InpaintingState,
setBoundingBoxDimensions,
} from '../../../tabs/Inpainting/inpaintingSlice';
const boundingBoxDimensionsSelector = createSelector(
(state: RootState) => state.inpainting,
(inpainting: InpaintingState) => {
const { canvasDimensions, boundingBoxDimensions } = inpainting;
return { canvasDimensions, boundingBoxDimensions };
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
const BoundingBoxDimensions = () => {
const dispatch = useAppDispatch();
const { canvasDimensions, boundingBoxDimensions } = useAppSelector(
boundingBoxDimensionsSelector
);
const handleChangeBoundingBoxWidth = (v: number) => {
dispatch(setBoundingBoxDimensions({ ...boundingBoxDimensions, width: v }));
};
const handleChangeBoundingBoxHeight = (v: number) => {
dispatch(setBoundingBoxDimensions({ ...boundingBoxDimensions, height: v }));
};
return (
<div className="inpainting-bounding-box-dimensions">
Inpainting Bounding Box
<div className="inpainting-bounding-box-dimensions-slider-numberinput">
<IAISlider
label="Width"
width={'8rem'}
min={64}
max={roundDownToMultiple(canvasDimensions.width, 64)}
step={64}
value={boundingBoxDimensions.width}
onChange={handleChangeBoundingBoxWidth}
/>
<IAINumberInput
value={boundingBoxDimensions.width}
onChange={handleChangeBoundingBoxWidth}
min={64}
max={roundDownToMultiple(canvasDimensions.width, 64)}
step={64}
width={'5.5rem'}
/>
</div>
<div className="inpainting-bounding-box-dimensions-slider-numberinput">
<IAISlider
label="Height"
width={'8rem'}
min={64}
max={roundDownToMultiple(canvasDimensions.height, 64)}
step={64}
value={boundingBoxDimensions.height}
onChange={handleChangeBoundingBoxHeight}
/>
<IAINumberInput
value={boundingBoxDimensions.height}
onChange={handleChangeBoundingBoxHeight}
min={64}
max={roundDownToMultiple(canvasDimensions.height, 64)}
step={64}
width={'5.5rem'}
/>
</div>
</div>
);
};
export default BoundingBoxDimensions;

View File

@ -0,0 +1,44 @@
.inpainting-bounding-box-settings {
display: flex;
flex-direction: column;
max-width: 100%;
border-radius: 0.4rem;
border: 2px solid var(--tab-color);
}
.inpainting-bounding-box-header {
background-color: var(--tab-color);
display: flex;
flex-direction: row;
justify-content: space-between;
padding: 0.5rem 1rem;
border-radius: 0.4rem 0.4rem 0 0;
p {
font-weight: bold;
}
}
.inpainting-bounding-box-settings-items {
padding: 1rem;
display: flex;
flex-direction: column;
row-gap: 1rem;
.inpainting-bounding-box-reset-icon-btn {
background-color: var(--btn-load-more) !important;
&:hover {
background-color: var(--btn-load-more-hover) !important;
}
}
}
.inpainting-bounding-box-dimensions-slider-numberinput {
display: flex;
justify-content: space-between;
column-gap: 1rem;
}
.inpainting-bounding-box-darken {
width: max-content;
}

View File

@ -0,0 +1,176 @@
import { createSelector } from '@reduxjs/toolkit';
import _ from 'lodash';
import { ChangeEvent } from 'react';
import { BiReset } from 'react-icons/bi';
import {
RootState,
useAppDispatch,
useAppSelector,
} from '../../../../app/store';
import IAICheckbox from '../../../../common/components/IAICheckbox';
import IAIIconButton from '../../../../common/components/IAIIconButton';
import IAINumberInput from '../../../../common/components/IAINumberInput';
import IAISlider from '../../../../common/components/IAISlider';
import IAISwitch from '../../../../common/components/IAISwitch';
import { roundDownToMultiple } from '../../../../common/util/roundDownToMultiple';
import {
InpaintingState,
setBoundingBoxDimensions,
setShouldShowBoundingBox,
setShouldShowBoundingBoxFill,
} from '../../../tabs/Inpainting/inpaintingSlice';
const boundingBoxDimensionsSelector = createSelector(
(state: RootState) => state.inpainting,
(inpainting: InpaintingState) => {
const {
canvasDimensions,
boundingBoxDimensions,
shouldShowBoundingBox,
shouldShowBoundingBoxFill,
pastLines,
futureLines,
} = inpainting;
return {
canvasDimensions,
boundingBoxDimensions,
shouldShowBoundingBox,
shouldShowBoundingBoxFill,
pastLines,
futureLines,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
const BoundingBoxSettings = () => {
const dispatch = useAppDispatch();
const {
canvasDimensions,
boundingBoxDimensions,
shouldShowBoundingBox,
shouldShowBoundingBoxFill,
} = useAppSelector(boundingBoxDimensionsSelector);
const handleChangeBoundingBoxWidth = (v: number) => {
dispatch(setBoundingBoxDimensions({ ...boundingBoxDimensions, width: v }));
};
const handleChangeBoundingBoxHeight = (v: number) => {
dispatch(setBoundingBoxDimensions({ ...boundingBoxDimensions, height: v }));
};
const handleShowBoundingBox = (e: ChangeEvent<HTMLInputElement>) =>
dispatch(setShouldShowBoundingBox(e.target.checked));
const handleChangeShouldShowBoundingBoxFill = () => {
dispatch(setShouldShowBoundingBoxFill(!shouldShowBoundingBoxFill));
};
const handleResetWidth = () => {
dispatch(
setBoundingBoxDimensions({
...boundingBoxDimensions,
width: canvasDimensions.width,
})
);
};
const handleResetHeight = () => {
dispatch(
setBoundingBoxDimensions({
...boundingBoxDimensions,
height: canvasDimensions.height,
})
);
};
return (
<div className="inpainting-bounding-box-settings">
<div className="inpainting-bounding-box-header">
<p>Inpaint Box</p>
<IAISwitch
isChecked={shouldShowBoundingBox}
width={'auto'}
onChange={handleShowBoundingBox}
/>
</div>
<div className="inpainting-bounding-box-settings-items">
<div className="inpainting-bounding-box-dimensions-slider-numberinput">
<IAISlider
label="Box W"
min={64}
max={roundDownToMultiple(canvasDimensions.width, 64)}
step={64}
value={boundingBoxDimensions.width}
onChange={handleChangeBoundingBoxWidth}
isDisabled={!shouldShowBoundingBox}
width={'5rem'}
/>
<IAINumberInput
value={boundingBoxDimensions.width}
onChange={handleChangeBoundingBoxWidth}
min={64}
max={roundDownToMultiple(canvasDimensions.width, 64)}
step={64}
isDisabled={!shouldShowBoundingBox}
width={'5rem'}
/>
<IAIIconButton
size={'sm'}
aria-label={'Reset Width'}
tooltip={'Reset Width'}
onClick={handleResetWidth}
icon={<BiReset />}
styleClass="inpainting-bounding-box-reset-icon-btn"
/>
</div>
<div className="inpainting-bounding-box-dimensions-slider-numberinput">
<IAISlider
label="Box H"
min={64}
max={roundDownToMultiple(canvasDimensions.height, 64)}
step={64}
value={boundingBoxDimensions.height}
onChange={handleChangeBoundingBoxHeight}
isDisabled={!shouldShowBoundingBox}
width={'5rem'}
/>
<IAINumberInput
value={boundingBoxDimensions.height}
onChange={handleChangeBoundingBoxHeight}
min={64}
max={roundDownToMultiple(canvasDimensions.height, 64)}
step={64}
padding="0"
isDisabled={!shouldShowBoundingBox}
width={'5rem'}
/>
<IAIIconButton
size={'sm'}
aria-label={'Reset Height'}
tooltip={'Reset Height'}
onClick={handleResetHeight}
icon={<BiReset />}
styleClass="inpainting-bounding-box-reset-icon-btn"
/>
</div>
<IAICheckbox
label="Darken Outside Box"
isChecked={shouldShowBoundingBoxFill}
onChange={handleChangeShouldShowBoundingBoxFill}
styleClass="inpainting-bounding-box-darken"
isDisabled={!shouldShowBoundingBox}
/>
</div>
</div>
);
};
export default BoundingBoxSettings;

View File

@ -0,0 +1,59 @@
import { useToast } from '@chakra-ui/react';
import { createSelector } from '@reduxjs/toolkit';
import React from 'react';
import {
RootState,
useAppDispatch,
useAppSelector,
} from '../../../../app/store';
import IAIButton from '../../../../common/components/IAIButton';
import {
InpaintingState,
setClearBrushHistory,
} from '../../../tabs/Inpainting/inpaintingSlice';
import BoundingBoxSettings from './BoundingBoxSettings';
import _ from 'lodash';
const inpaintingSelector = createSelector(
(state: RootState) => state.inpainting,
(inpainting: InpaintingState) => {
const { pastLines, futureLines } = inpainting;
return {
pastLines,
futureLines,
};
},
{
memoizeOptions: {
resultEqualityCheck: _.isEqual,
},
}
);
export default function InpaintingSettings() {
const dispatch = useAppDispatch();
const toast = useToast();
const { pastLines, futureLines } = useAppSelector(inpaintingSelector);
const handleClearBrushHistory = () => {
dispatch(setClearBrushHistory());
toast({
title: 'Brush Stroke History Cleared',
status: 'success',
duration: 2500,
isClosable: true,
});
};
return (
<>
<BoundingBoxSettings />
<IAIButton
label="Clear Brush History"
onClick={handleClearBrushHistory}
tooltip="Clears brush stroke history"
disabled={futureLines.length > 0 || pastLines.length > 0 ? false : true}
/>
</>
);
}

View File

@ -61,6 +61,7 @@ const InpaintingCanvas = () => {
shouldShowBoundingBoxFill, shouldShowBoundingBoxFill,
isDrawing, isDrawing,
isBoundingBoxTransforming, isBoundingBoxTransforming,
shouldShowBoundingBox,
} = useAppSelector(inpaintingCanvasSelector); } = useAppSelector(inpaintingCanvasSelector);
// set the closure'd refs // set the closure'd refs
@ -280,10 +281,10 @@ const InpaintingCanvas = () => {
)} )}
</Layer> </Layer>
<Layer> <Layer>
{shouldShowBoundingBoxFill && ( {shouldShowBoundingBox && shouldShowBoundingBoxFill && (
<InpaintingBoundingBoxPreviewOverlay /> <InpaintingBoundingBoxPreviewOverlay />
)} )}
<InpaintingBoundingBoxPreview /> {shouldShowBoundingBox && <InpaintingBoundingBoxPreview />}
<InpaintingCanvasBrushPreviewOutline /> <InpaintingCanvasBrushPreviewOutline />
</Layer> </Layer>
</> </>

View File

@ -6,8 +6,6 @@ import {
FaPalette, FaPalette,
FaPlus, FaPlus,
FaRedo, FaRedo,
FaTint,
FaTintSlash,
FaUndo, FaUndo,
} from 'react-icons/fa'; } from 'react-icons/fa';
import { BiHide, BiShow } from 'react-icons/bi'; import { BiHide, BiShow } from 'react-icons/bi';
@ -25,7 +23,6 @@ import {
setShouldShowMask, setShouldShowMask,
setShouldInvertMask, setShouldInvertMask,
setNeedsRepaint, setNeedsRepaint,
setShouldShowBoundingBoxFill,
} from './inpaintingSlice'; } from './inpaintingSlice';
import { MdInvertColors, MdInvertColorsOff } from 'react-icons/md'; import { MdInvertColors, MdInvertColorsOff } from 'react-icons/md';
@ -49,7 +46,6 @@ const InpaintingControls = () => {
isMaskEmpty, isMaskEmpty,
activeTabName, activeTabName,
showDualDisplay, showDualDisplay,
shouldShowBoundingBoxFill,
} = useAppSelector(inpaintingControlsSelector); } = useAppSelector(inpaintingControlsSelector);
const dispatch = useAppDispatch(); const dispatch = useAppDispatch();
@ -268,10 +264,6 @@ const InpaintingControls = () => {
dispatch(setNeedsRepaint(true)); dispatch(setNeedsRepaint(true));
}; };
const handleChangeShouldShowBoundingBoxFill = () => {
dispatch(setShouldShowBoundingBoxFill(!shouldShowBoundingBoxFill));
};
return ( return (
<div className="inpainting-settings"> <div className="inpainting-settings">
<div className="inpainting-buttons"> <div className="inpainting-buttons">
@ -390,13 +382,6 @@ const InpaintingControls = () => {
data-selected={showDualDisplay} data-selected={showDualDisplay}
onClick={handleDualDisplay} onClick={handleDualDisplay}
/> />
<IAIIconButton
aria-label="Darken Outside Bounding Box (xxx)"
tooltip="Darken Outside Bounding Box (xxx)"
icon={shouldShowBoundingBoxFill ? <FaTint /> : <FaTintSlash />}
data-selected={shouldShowBoundingBoxFill}
onClick={handleChangeShouldShowBoundingBoxFill}
/>
</div> </div>
</div> </div>
</div> </div>

View File

@ -3,7 +3,7 @@ import { RootState, useAppSelector } from '../../../app/store';
import FaceRestoreHeader from '../../options/AdvancedOptions/FaceRestore/FaceRestoreHeader'; import FaceRestoreHeader from '../../options/AdvancedOptions/FaceRestore/FaceRestoreHeader';
import FaceRestoreOptions from '../../options/AdvancedOptions/FaceRestore/FaceRestoreOptions'; import FaceRestoreOptions from '../../options/AdvancedOptions/FaceRestore/FaceRestoreOptions';
import ImageToImageStrength from '../../options/AdvancedOptions/ImageToImage/ImageToImageStrength'; import ImageToImageStrength from '../../options/AdvancedOptions/ImageToImage/ImageToImageStrength';
import BoundingBoxDimensions from '../../options/AdvancedOptions/Inpainting/BoundingBoxDimensions'; import InpaintingSettings from '../../options/AdvancedOptions/Inpainting/InpaintingSettings';
import SeedHeader from '../../options/AdvancedOptions/Seed/SeedHeader'; import SeedHeader from '../../options/AdvancedOptions/Seed/SeedHeader';
import SeedOptions from '../../options/AdvancedOptions/Seed/SeedOptions'; import SeedOptions from '../../options/AdvancedOptions/Seed/SeedOptions';
import UpscaleHeader from '../../options/AdvancedOptions/Upscale/UpscaleHeader'; import UpscaleHeader from '../../options/AdvancedOptions/Upscale/UpscaleHeader';
@ -49,11 +49,11 @@ export default function InpaintingPanel() {
<PromptInput /> <PromptInput />
<ProcessButtons /> <ProcessButtons />
<MainOptions /> <MainOptions />
<BoundingBoxDimensions />
<ImageToImageStrength <ImageToImageStrength
label="Image To Image Strength" label="Image To Image Strength"
styleClass="main-option-block image-to-image-strength-main-option" styleClass="main-option-block image-to-image-strength-main-option"
/> />
<InpaintingSettings />
<MainAdvancedOptionsCheckbox /> <MainAdvancedOptionsCheckbox />
{showAdvancedOptions ? ( {showAdvancedOptions ? (
<OptionsAccordion accordionInfo={imageToImageAccordions} /> <OptionsAccordion accordionInfo={imageToImageAccordions} />

View File

@ -38,6 +38,7 @@ export interface InpaintingState {
boundingBoxCoordinate: Vector2d; boundingBoxCoordinate: Vector2d;
isMovingBoundingBox: boolean; isMovingBoundingBox: boolean;
boundingBoxPreviewFill: RgbaColor; boundingBoxPreviewFill: RgbaColor;
shouldShowBoundingBox: boolean;
shouldShowBoundingBoxFill: boolean; shouldShowBoundingBoxFill: boolean;
isBoundingBoxTransforming: boolean; isBoundingBoxTransforming: boolean;
lines: MaskLine[]; lines: MaskLine[];
@ -62,6 +63,7 @@ const initialInpaintingState: InpaintingState = {
boundingBoxDimensions: { width: 64, height: 64 }, boundingBoxDimensions: { width: 64, height: 64 },
boundingBoxCoordinate: { x: 0, y: 0 }, boundingBoxCoordinate: { x: 0, y: 0 },
boundingBoxPreviewFill: { r: 0, g: 0, b: 0, a: 0.7 }, boundingBoxPreviewFill: { r: 0, g: 0, b: 0, a: 0.7 },
shouldShowBoundingBox: false,
shouldShowBoundingBoxFill: false, shouldShowBoundingBoxFill: false,
isBoundingBoxTransforming: false, isBoundingBoxTransforming: false,
cursorPosition: null, cursorPosition: null,
@ -310,6 +312,13 @@ export const inpaintingSlice = createSlice({
setIsDrawing: (state, action: PayloadAction<boolean>) => { setIsDrawing: (state, action: PayloadAction<boolean>) => {
state.isDrawing = action.payload; state.isDrawing = action.payload;
}, },
setShouldShowBoundingBox: (state, action: PayloadAction<boolean>) => {
state.shouldShowBoundingBox = action.payload;
},
setClearBrushHistory: (state) => {
state.pastLines = [];
state.futureLines = [];
},
}, },
}); });
@ -341,6 +350,8 @@ export const {
setIsBoundingBoxTransforming, setIsBoundingBoxTransforming,
setIsDrawing, setIsDrawing,
setShouldShowBrush, setShouldShowBrush,
setShouldShowBoundingBox,
setClearBrushHistory,
} = inpaintingSlice.actions; } = inpaintingSlice.actions;
export default inpaintingSlice.reducer; export default inpaintingSlice.reducer;

View File

@ -79,6 +79,7 @@ export const inpaintingCanvasSelector = createSelector(
shouldShowBoundingBoxFill, shouldShowBoundingBoxFill,
isDrawing, isDrawing,
isBoundingBoxTransforming, isBoundingBoxTransforming,
shouldShowBoundingBox,
} = inpainting; } = inpainting;
return { return {
tool, tool,
@ -97,6 +98,7 @@ export const inpaintingCanvasSelector = createSelector(
shouldShowBoundingBoxFill, shouldShowBoundingBoxFill,
isDrawing, isDrawing,
isBoundingBoxTransforming, isBoundingBoxTransforming,
shouldShowBoundingBox,
}; };
}, },
{ {

View File

@ -22,7 +22,7 @@
@use '../features/options/MainOptions/MainOptions.scss'; @use '../features/options/MainOptions/MainOptions.scss';
@use '../features/options/AccordionItems/AdvancedSettings.scss'; @use '../features/options/AccordionItems/AdvancedSettings.scss';
@use '../features/options/AdvancedOptions/Upscale/UpscaleOptions.scss'; @use '../features/options/AdvancedOptions/Upscale/UpscaleOptions.scss';
@use '../features/options/AdvancedOptions/Inpainting/BoundingBoxDimensions.scss'; @use '../features/options/AdvancedOptions/Inpainting/BoundingBoxSettings.scss';
@use '../features/system/ProgressBar.scss'; @use '../features/system/ProgressBar.scss';
// gallery // gallery