Merges development

This commit is contained in:
psychedelicious
2022-10-27 15:24:00 +11:00
parent 7b46d5f823
commit 44599a239f
119 changed files with 4981 additions and 1058 deletions

View File

@@ -25,7 +25,10 @@ const systemSelector = createSelector(
const GuidePopover = ({ children, feature }: GuideProps) => {
const shouldDisplayGuides = useAppSelector(systemSelector);
const { text } = FEATURES[feature];
return shouldDisplayGuides ? (
if (!shouldDisplayGuides) return null;
return (
<Popover trigger={'hover'}>
<PopoverTrigger>
<Box>{children}</Box>
@@ -40,8 +43,6 @@ const GuidePopover = ({ children, feature }: GuideProps) => {
<div className="guide-popover-guide-content">{text}</div>
</PopoverContent>
</Popover>
) : (
<></>
);
};

View File

@@ -0,0 +1,24 @@
.invokeai__checkbox {
.chakra-checkbox__label {
margin-top: 1px;
}
.chakra-checkbox__control {
width: 1rem;
height: 1rem;
border: none;
border-radius: 0.2rem;
background-color: var(--input-checkbox-bg);
svg {
width: 0.6rem;
height: 0.6rem;
stroke-width: 3px !important;
}
&[data-checked] {
color: var(--text-color);
background-color: var(--input-checkbox-checked-bg);
}
}
}

View File

@@ -0,0 +1,17 @@
import { Checkbox, CheckboxProps } from '@chakra-ui/react';
type IAICheckboxProps = CheckboxProps & {
label: string;
styleClass?: string;
};
const IAICheckbox = (props: IAICheckboxProps) => {
const { label, styleClass, ...rest } = props;
return (
<Checkbox className={`invokeai__checkbox ${styleClass}`} {...rest}>
{label}
</Checkbox>
);
};
export default IAICheckbox;

View File

@@ -0,0 +1,8 @@
.invokeai__color-picker {
.react-colorful__hue-pointer,
.react-colorful__saturation-pointer {
width: 1.5rem;
height: 1.5rem;
border-color: var(--white);
}
}

View File

@@ -0,0 +1,19 @@
import { RgbaColorPicker } from 'react-colorful';
import { ColorPickerBaseProps, RgbaColor } from 'react-colorful/dist/types';
type IAIColorPickerProps = ColorPickerBaseProps<RgbaColor> & {
styleClass?: string;
};
const IAIColorPicker = (props: IAIColorPickerProps) => {
const { styleClass, ...rest } = props;
return (
<RgbaColorPicker
className={`invokeai__color-picker ${styleClass}`}
{...rest}
/>
);
};
export default IAIColorPicker;

View File

@@ -0,0 +1,20 @@
@use '../../styles/Mixins/' as *;
.icon-button {
background-color: var(--btn-grey);
cursor: pointer;
&:hover {
background-color: var(--btn-grey-hover);
}
&[data-selected=true] {
background-color: var(--accent-color);
&:hover {
background-color: var(--accent-color-hover);
}
}
&[disabled] {
cursor: not-allowed;
}
}

View File

@@ -8,20 +8,28 @@ import {
interface Props extends IconButtonProps {
tooltip?: string;
tooltipPlacement?: PlacementWithLogical | undefined;
styleClass?: string;
}
/**
* Reusable customized button component. Originally was more customized - now probably unecessary.
*
* TODO: Get rid of this.
*/
const IAIIconButton = (props: Props) => {
const { tooltip = '', tooltipPlacement = 'bottom', onClick, ...rest } = props;
const {
tooltip = '',
tooltipPlacement = 'top',
styleClass,
onClick,
cursor,
...rest
} = props;
return (
<Tooltip label={tooltip} hasArrow placement={tooltipPlacement}>
<IconButton
className={`icon-button ${styleClass}`}
{...rest}
cursor={onClick ? 'pointer' : 'unset'}
cursor={cursor ? cursor : onClick ? 'pointer' : 'unset'}
onClick={onClick}
/>
</Tooltip>

View File

@@ -17,8 +17,8 @@
&:focus {
outline: none;
border: 2px solid var(--prompt-border-color);
box-shadow: 0 0 10px 0 var(--prompt-box-shadow-color);
border: 2px solid var(--input-border-color);
box-shadow: 0 0 10px 0 var(--input-box-shadow-color);
}
&:disabled {

View File

@@ -1,15 +1,32 @@
.number-input {
.invokeai__number-input-form-control {
display: grid;
grid-template-columns: max-content auto;
column-gap: 1rem;
align-items: center;
.number-input-label {
.invokeai__number-input-form-label {
color: var(--text-color-secondary);
margin-right: 0;
font-size: 1rem;
margin-bottom: 0;
flex-grow: 2;
white-space: nowrap;
&[data-focus] + .invokeai__number-input-root {
outline: none;
border: 2px solid var(--input-border-color);
box-shadow: 0 0 10px 0 var(--input-box-shadow-color);
}
&[aria-invalid='true'] + .invokeai__number-input-root {
outline: none;
border: 2px solid var(--border-color-invalid);
box-shadow: 0 0 10px 0 var(--box-shadow-color-invalid);
}
}
.number-input-field {
.invokeai__number-input-root {
height: 2rem;
display: grid;
grid-template-columns: auto max-content;
column-gap: 0.5rem;
@@ -19,34 +36,45 @@
border-radius: 0.2rem;
}
.number-input-entry {
.invokeai__number-input-field {
border: none;
font-weight: bold;
width: 100%;
padding-inline-end: 0;
height: auto;
padding: 0;
font-size: 0.9rem;
padding-left: 0.5rem;
padding-right: 0.5rem;
&:focus {
outline: none;
border: 2px solid var(--prompt-border-color);
box-shadow: 0 0 10px 0 var(--prompt-box-shadow-color);
box-shadow: none;
}
&:disabled {
opacity: 0.2;
}
}
.number-input-stepper {
.invokeai__number-input-stepper {
display: grid;
padding-right: 0.7rem;
padding-right: 0.5rem;
svg {
width: 12px;
height: 12px;
}
.number-input-stepper-button {
.invokeai__number-input-stepper-button {
border: none;
// expand arrow hitbox
padding: 0 0.5rem;
margin: 0 -0.5rem;
svg {
width: 10px;
height: 10px;
}
&:hover {
path {
// fill: ;
}
}
}
}
}

View File

@@ -6,6 +6,12 @@ import {
NumberDecrementStepper,
NumberInputProps,
FormLabel,
NumberInputFieldProps,
NumberInputStepperProps,
FormControlProps,
FormLabelProps,
TooltipProps,
Tooltip,
} from '@chakra-ui/react';
import _ from 'lodash';
import { FocusEvent, useEffect, useState } from 'react';
@@ -23,6 +29,12 @@ interface Props extends Omit<NumberInputProps, 'onChange'> {
max: number;
clamp?: boolean;
isInteger?: boolean;
formControlProps?: FormControlProps;
formLabelProps?: FormLabelProps;
numberInputProps?: NumberInputProps;
numberInputFieldProps?: NumberInputFieldProps;
numberInputStepperProps?: NumberInputStepperProps;
tooltipProps?: Omit<TooltipProps, 'children'>;
}
/**
@@ -34,8 +46,6 @@ const IAINumberInput = (props: Props) => {
styleClass,
isDisabled = false,
showStepper = true,
fontSize = '1rem',
size = 'sm',
width,
textAlign,
isInvalid,
@@ -44,6 +54,11 @@ const IAINumberInput = (props: Props) => {
min,
max,
isInteger = true,
formControlProps,
formLabelProps,
numberInputFieldProps,
numberInputStepperProps,
tooltipProps,
...rest
} = props;
@@ -65,7 +80,10 @@ const IAINumberInput = (props: Props) => {
* from the current value.
*/
useEffect(() => {
if (!valueAsString.match(numberStringRegex) && value !== Number(valueAsString)) {
if (
!valueAsString.match(numberStringRegex) &&
value !== Number(valueAsString)
) {
setValueAsString(String(value));
}
}, [value, valueAsString]);
@@ -94,47 +112,51 @@ const IAINumberInput = (props: Props) => {
};
return (
<FormControl
isDisabled={isDisabled}
isInvalid={isInvalid}
className={`number-input ${styleClass}`}
>
{label && (
<Tooltip {...tooltipProps}>
<FormControl
isDisabled={isDisabled}
isInvalid={isInvalid}
className={`invokeai__number-input-form-control ${styleClass}`}
{...formControlProps}
>
<FormLabel
fontSize={fontSize}
marginBottom={1}
flexGrow={2}
whiteSpace="nowrap"
className="number-input-label"
className="invokeai__number-input-form-label"
style={{ display: label ? 'block' : 'none' }}
{...formLabelProps}
>
{label}
</FormLabel>
)}
<NumberInput
size={size}
{...rest}
className="number-input-field"
value={valueAsString}
keepWithinRange={true}
clampValueOnBlur={false}
onChange={handleOnChange}
onBlur={handleBlur}
>
<NumberInputField
fontSize={fontSize}
className="number-input-entry"
<NumberInput
className="invokeai__number-input-root"
value={valueAsString}
keepWithinRange={true}
clampValueOnBlur={false}
onChange={handleOnChange}
onBlur={handleBlur}
width={width}
textAlign={textAlign}
/>
<div
className="number-input-stepper"
style={showStepper ? { display: 'block' } : { display: 'none' }}
{...rest}
>
<NumberIncrementStepper className="number-input-stepper-button" />
<NumberDecrementStepper className="number-input-stepper-button" />
</div>
</NumberInput>
</FormControl>
<NumberInputField
className="invokeai__number-input-field"
textAlign={textAlign}
{...numberInputFieldProps}
/>
<div
className="invokeai__number-input-stepper"
style={showStepper ? { display: 'block' } : { display: 'none' }}
>
<NumberIncrementStepper
{...numberInputStepperProps}
className="invokeai__number-input-stepper-button"
/>
<NumberDecrementStepper
{...numberInputStepperProps}
className="invokeai__number-input-stepper-button"
/>
</div>
</NumberInput>
</FormControl>
</Tooltip>
);
};

View File

@@ -0,0 +1,12 @@
.invokeai__popover-content {
min-width: unset;
width: unset !important;
padding: 1rem;
border-radius: 0.5rem !important;
background-color: var(--background-color) !important;
border: 2px solid var(--border-color) !important;
.invokeai__popover-arrow {
background-color: var(--background-color) !important;
}
}

View File

@@ -0,0 +1,39 @@
import {
Popover,
PopoverArrow,
PopoverContent,
PopoverTrigger,
Box,
} from '@chakra-ui/react';
import { PopoverProps } from '@chakra-ui/react';
import { ReactNode } from 'react';
type IAIPopoverProps = PopoverProps & {
triggerComponent: ReactNode;
children: ReactNode;
styleClass?: string;
hasArrow?: boolean;
};
const IAIPopover = (props: IAIPopoverProps) => {
const {
triggerComponent,
children,
styleClass,
hasArrow = true,
...rest
} = props;
return (
<Popover {...rest}>
<PopoverTrigger>
<Box>{triggerComponent}</Box>
</PopoverTrigger>
<PopoverContent className={`invokeai__popover-content ${styleClass}`}>
{hasArrow && <PopoverArrow className={'invokeai__popover-arrow'} />}
{children}
</PopoverContent>
</Popover>
);
};
export default IAIPopover;

View File

@@ -1,28 +1,32 @@
.iai-select {
@use '../../styles/Mixins/' as *;
.invokeai__select {
display: grid;
grid-template-columns: repeat(2, max-content);
column-gap: 1rem;
align-items: center;
width: max-content;
.iai-select-label {
.invokeai__select-label {
color: var(--text-color-secondary);
margin-right: 0;
}
.iai-select-picker {
.invokeai__select-picker {
border: 2px solid var(--border-color);
background-color: var(--background-color-secondary);
font-weight: bold;
height: 2rem;
border-radius: 0.2rem;
&:focus {
outline: none;
border: 2px solid var(--prompt-border-color);
box-shadow: 0 0 10px 0 var(--prompt-box-shadow-color);
border: 2px solid var(--input-border-color);
box-shadow: 0 0 10px 0 var(--input-box-shadow-color);
}
}
.iai-select-option {
.invokeai__select-option {
background-color: var(--background-color-secondary);
}
}

View File

@@ -21,13 +21,13 @@ const IAISelect = (props: Props) => {
...rest
} = props;
return (
<FormControl isDisabled={isDisabled} className={`iai-select ${styleClass}`}>
<FormControl isDisabled={isDisabled} className={`invokeai__select ${styleClass}`}>
<FormLabel
fontSize={fontSize}
marginBottom={1}
flexGrow={2}
whiteSpace="nowrap"
className="iai-select-label"
className="invokeai__select-label"
>
{label}
</FormLabel>
@@ -35,11 +35,11 @@ const IAISelect = (props: Props) => {
fontSize={fontSize}
size={size}
{...rest}
className="iai-select-picker"
className="invokeai__select-picker"
>
{validValues.map((opt) => {
return typeof opt === 'string' || typeof opt === 'number' ? (
<option key={opt} value={opt} className="iai-select-option">
<option key={opt} value={opt} className="invokeai__select-option">
{opt}
</option>
) : (

View File

@@ -0,0 +1,40 @@
@use '../../styles/Mixins/' as *;
.invokeai__slider-form-control {
display: flex;
column-gap: 1rem;
justify-content: space-between;
align-items: center;
width: max-content;
padding-right: 0.25rem;
.invokeai__slider-inner-container {
display: flex;
column-gap: 0.5rem;
.invokeai__slider-form-label {
color: var(--text-color-secondary);
margin: 0;
margin-right: 0.5rem;
margin-bottom: 0.1rem;
}
.invokeai__slider-root {
.invokeai__slider-filled-track {
background-color: var(--accent-color-hover);
}
.invokeai__slider-track {
background-color: var(--text-color-secondary);
height: 5px;
border-radius: 9999px;
}
.invokeai__slider-thumb {
}
}
}
}
.invokeai__slider-thumb-tooltip {
}

View File

@@ -0,0 +1,88 @@
import {
Slider,
SliderTrack,
SliderFilledTrack,
SliderThumb,
FormControl,
FormLabel,
Tooltip,
SliderProps,
FormControlProps,
FormLabelProps,
SliderTrackProps,
SliderThumbProps,
TooltipProps,
SliderInnerTrackProps,
} from '@chakra-ui/react';
type IAISliderProps = SliderProps & {
label?: string;
styleClass?: string;
formControlProps?: FormControlProps;
formLabelProps?: FormLabelProps;
sliderTrackProps?: SliderTrackProps;
sliderInnerTrackProps?: SliderInnerTrackProps;
sliderThumbProps?: SliderThumbProps;
sliderThumbTooltipProps?: Omit<TooltipProps, 'children'>;
};
const IAISlider = (props: IAISliderProps) => {
const {
label,
styleClass,
formControlProps,
formLabelProps,
sliderTrackProps,
sliderInnerTrackProps,
sliderThumbProps,
sliderThumbTooltipProps,
...rest
} = props;
return (
<FormControl
className={`invokeai__slider-form-control ${styleClass}`}
{...formControlProps}
>
<div className="invokeai__slider-inner-container">
<FormLabel
className={`invokeai__slider-form-label`}
whiteSpace="nowrap"
{...formLabelProps}
>
{label}
</FormLabel>
<Slider
className={`invokeai__slider-root`}
aria-label={label}
focusThumbOnChange={false}
{...rest}
>
<SliderTrack
className={`invokeai__slider-track`}
{...sliderTrackProps}
>
<SliderFilledTrack
className={`invokeai__slider-filled-track`}
{...sliderInnerTrackProps}
/>
</SliderTrack>
<Tooltip
className={`invokeai__slider-thumb-tooltip`}
placement="top"
hasArrow
{...sliderThumbTooltipProps}
>
<SliderThumb
className={`invokeai__slider-thumb`}
{...sliderThumbProps}
/>
</Tooltip>
</Slider>
</div>
</FormControl>
);
};
export default IAISlider;

View File

@@ -1,18 +1,32 @@
.chakra-switch,
.switch-button {
span {
background-color: var(--switch-bg-color);
.invokeai__switch-form-control {
.invokeai__switch-form-label {
display: flex;
column-gap: 1rem;
justify-content: space-between;
align-items: center;
color: var(--text-color-secondary);
font-size: 1rem;
margin-right: 0;
margin-bottom: 0.1rem;
white-space: nowrap;
span {
background-color: var(--white);
}
}
.invokeai__switch-root {
span {
background-color: var(--switch-bg-color);
span {
background-color: var(--white);
}
}
span[data-checked] {
background: var(--switch-bg-active-color);
&[data-checked] {
span {
background: var(--switch-bg-active-color);
span {
background-color: var(--white);
span {
background-color: var(--white);
}
}
}
}
}
}

View File

@@ -24,20 +24,24 @@ const IAISwitch = (props: Props) => {
...rest
} = props;
return (
<FormControl isDisabled={isDisabled} width={width}>
<Flex justifyContent={'space-between'} alignItems={'center'}>
{label && (
<FormLabel
fontSize={fontSize}
marginBottom={1}
flexGrow={2}
whiteSpace="nowrap"
>
{label}
</FormLabel>
)}
<Switch size={size} className="switch-button" {...rest} />
</Flex>
<FormControl
isDisabled={isDisabled}
width={width}
className="invokeai__switch-form-control"
>
<FormLabel
className="invokeai__switch-form-label"
fontSize={fontSize}
whiteSpace="nowrap"
>
{label}
<Switch
className="invokeai__switch-root"
size={size}
// className="switch-button"
{...rest}
/>
</FormLabel>
</FormControl>
);
};

View File

@@ -0,0 +1,62 @@
.invokeai__slider-root {
position: relative;
display: flex;
align-items: center;
user-select: none;
touch-action: none;
width: 200px;
&[data-orientation='horizontal'] {
height: 20px;
}
&[data-orientation='vertical'] {
width: 20px;
height: 200px;
}
.invokeai__slider-track {
background-color: black;
position: relative;
flex-grow: 1;
border-radius: 9999px;
&[data-orientation='horizontal'] {
height: 0.25rem;
}
&[data-orientation='vertical'] {
width: 0.25rem;
}
.invokeai__slider-range {
position: absolute;
background-color: white;
border-radius: 9999px;
height: 100%;
}
}
.invokeai__slider-thumb {
display: flex;
align-items: center;
.invokeai__slider-thumb-div {
all: unset;
display: block;
width: 1rem;
height: 1rem;
background-color: white;
box-shadow: 0 2px 10px rgba(0, 2, 10, 0.3);
border-radius: 100%;
&:hover {
background-color: violet;
}
&:focus {
box-shadow: 0 0 0 5px rgba(0, 2, 10, 0.3);
}
}
}
}

View File

@@ -0,0 +1,46 @@
import { Tooltip } from '@chakra-ui/react';
import * as Slider from '@radix-ui/react-slider';
import React from 'react';
import IAITooltip from './IAITooltip';
type IAISliderProps = Slider.SliderProps & {
value: number[];
tooltipLabel?: string;
orientation?: 'horizontal' | 'vertial';
trackProps?: Slider.SliderTrackProps;
rangeProps?: Slider.SliderRangeProps;
thumbProps?: Slider.SliderThumbProps;
};
const _IAISlider = (props: IAISliderProps) => {
const {
value,
tooltipLabel,
orientation,
trackProps,
rangeProps,
thumbProps,
...rest
} = props;
return (
<Slider.Root
className="invokeai__slider-root"
{...rest}
data-orientation={orientation || 'horizontal'}
>
<Slider.Track {...trackProps} className="invokeai__slider-track">
<Slider.Range {...rangeProps} className="invokeai__slider-range" />
</Slider.Track>
<Tooltip label={tooltipLabel ?? value[0]} placement="top">
<Slider.Thumb {...thumbProps} className="invokeai__slider-thumb">
<div className="invokeai__slider-thumb-div" />
{/*<IAITooltip trigger={<div className="invokeai__slider-thumb-div" />}>
{value && value[0]}
</IAITooltip>*/}
</Slider.Thumb>
</Tooltip>
</Slider.Root>
);
};
export default _IAISlider;

View File

@@ -0,0 +1,8 @@
.invokeai__tooltip-content {
padding: 0.5rem;
background-color: grey;
border-radius: 0.25rem;
.invokeai__tooltip-arrow {
background-color: grey;
}
}

View File

@@ -0,0 +1,35 @@
import * as Tooltip from '@radix-ui/react-tooltip';
import { ReactNode } from 'react';
type IAITooltipProps = Tooltip.TooltipProps & {
trigger: ReactNode;
children: ReactNode;
triggerProps?: Tooltip.TooltipTriggerProps;
contentProps?: Tooltip.TooltipContentProps;
arrowProps?: Tooltip.TooltipArrowProps;
};
const IAITooltip = (props: IAITooltipProps) => {
const { trigger, children, triggerProps, contentProps, arrowProps, ...rest } =
props;
return (
<Tooltip.Provider>
<Tooltip.Root {...rest} delayDuration={0}>
<Tooltip.Trigger {...triggerProps}>{trigger}</Tooltip.Trigger>
<Tooltip.Portal>
<Tooltip.Content
{...contentProps}
onPointerDownOutside={(e: any) => {e.preventDefault()}}
className="invokeai__tooltip-content"
>
<Tooltip.Arrow {...arrowProps} className="invokeai__tooltip-arrow" />
{children}
</Tooltip.Content>
</Tooltip.Portal>
</Tooltip.Root>
</Tooltip.Provider>
);
};
export default IAITooltip;

View File

@@ -3,9 +3,12 @@ import { isEqual } from 'lodash';
import { useMemo } from 'react';
import { useAppSelector } from '../../app/store';
import { RootState } from '../../app/store';
import { GalleryState } from '../../features/gallery/gallerySlice';
import { OptionsState } from '../../features/options/optionsSlice';
import { SystemState } from '../../features/system/systemSlice';
import { InpaintingState } from '../../features/tabs/Inpainting/inpaintingSlice';
import { tabMap } from '../../features/tabs/InvokeTabs';
import { validateSeedWeights } from '../util/seedWeightPairs';
export const optionsSelector = createSelector(
@@ -18,7 +21,7 @@ export const optionsSelector = createSelector(
maskPath: options.maskPath,
initialImagePath: options.initialImagePath,
seed: options.seed,
activeTab: options.activeTab,
activeTabName: tabMap[options.activeTab],
};
},
{
@@ -43,31 +46,66 @@ export const systemSelector = createSelector(
}
);
export const inpaintingSelector = createSelector(
(state: RootState) => state.inpainting,
(inpainting: InpaintingState) => {
return {
isMaskEmpty: inpainting.lines.length === 0,
};
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
}
);
export const gallerySelector = createSelector(
(state: RootState) => state.gallery,
(gallery: GalleryState) => {
return {
hasCurrentImage: Boolean(gallery.currentImage),
};
},
{
memoizeOptions: {
resultEqualityCheck: isEqual,
},
}
);
/**
* Checks relevant pieces of state to confirm generation will not deterministically fail.
* This is used to prevent the 'Generate' button from being clicked.
*/
const useCheckParameters = (): boolean => {
const { prompt } = useAppSelector(optionsSelector);
const {
prompt,
shouldGenerateVariations,
seedWeights,
maskPath,
initialImagePath,
seed,
activeTab,
activeTabName,
} = useAppSelector(optionsSelector);
const { isProcessing, isConnected } = useAppSelector(systemSelector);
const { isMaskEmpty } = useAppSelector(inpaintingSelector);
const { hasCurrentImage } = useAppSelector(gallerySelector);
return useMemo(() => {
// Cannot generate without a prompt
if (!prompt || Boolean(prompt.match(/^[\s\r\n]+$/))) {
return false;
}
if (prompt && !initialImagePath && activeTab === 1) {
if (activeTabName === 'img2img' && !initialImagePath) {
return false;
}
if (activeTabName === 'inpainting' && (!hasCurrentImage || isMaskEmpty)) {
return false;
}
@@ -106,7 +144,9 @@ const useCheckParameters = (): boolean => {
shouldGenerateVariations,
seedWeights,
seed,
activeTab,
activeTabName,
hasCurrentImage,
isMaskEmpty,
]);
};

View File

@@ -1,22 +1,38 @@
/*
These functions translate frontend state into parameters
suitable for consumption by the backend, and vice-versa.
*/
import { NUMPY_RAND_MAX, NUMPY_RAND_MIN } from '../../app/constants';
import { OptionsState } from '../../features/options/optionsSlice';
import { SystemState } from '../../features/system/systemSlice';
import {
seedWeightsToString,
stringToSeedWeightsArray,
} from './seedWeightPairs';
import { stringToSeedWeightsArray } from './seedWeightPairs';
import randomInt from './randomInt';
import { InvokeTabName } from '../../features/tabs/InvokeTabs';
import { InpaintingState } from '../../features/tabs/Inpainting/inpaintingSlice';
import generateMask from '../../features/tabs/Inpainting/util/generateMask';
export type FrontendToBackendParametersConfig = {
generationMode: InvokeTabName;
optionsState: OptionsState;
inpaintingState: InpaintingState;
systemState: SystemState;
imageToProcessUrl?: string;
maskImageElement?: HTMLImageElement;
};
/**
* Translates/formats frontend state into parameters suitable
* for consumption by the API.
*/
export const frontendToBackendParameters = (
optionsState: OptionsState,
systemState: SystemState
config: FrontendToBackendParametersConfig
): { [key: string]: any } => {
const {
generationMode,
optionsState,
inpaintingState,
systemState,
imageToProcessUrl,
maskImageElement,
} = config;
const {
prompt,
iterations,
@@ -30,10 +46,8 @@ export const frontendToBackendParameters = (
seed,
seamless,
hiresFix,
shouldUseInitImage,
img2imgStrength,
initialImagePath,
maskPath,
shouldFitToWidthHeight,
shouldGenerateVariations,
variationAmount,
@@ -61,8 +75,6 @@ export const frontendToBackendParameters = (
width,
sampler_name: sampler,
seed,
seamless,
hires_fix: hiresFix,
progress_images: shouldDisplayInProgress,
};
@@ -70,13 +82,45 @@ export const frontendToBackendParameters = (
? randomInt(NUMPY_RAND_MIN, NUMPY_RAND_MAX)
: seed;
if (shouldUseInitImage) {
// parameters common to txt2img and img2img
if (['txt2img', 'img2img'].includes(generationMode)) {
generationParameters.seamless = seamless;
generationParameters.hires_fix = hiresFix;
}
// img2img exclusive parameters
if (generationMode === 'img2img') {
generationParameters.init_img = initialImagePath;
generationParameters.strength = img2imgStrength;
generationParameters.fit = shouldFitToWidthHeight;
if (maskPath) {
generationParameters.init_mask = maskPath;
}
}
// inpainting exclusive parameters
if (generationMode === 'inpainting' && maskImageElement) {
const {
lines,
boundingBoxCoordinate: { x, y },
boundingBoxDimensions: { width, height },
} = inpaintingState;
const boundingBox = {
x,
y,
width,
height,
};
generationParameters.init_img = imageToProcessUrl;
generationParameters.strength = img2imgStrength;
generationParameters.fit = false;
const maskDataURL = generateMask(maskImageElement, lines, boundingBox);
generationParameters.init_mask = maskDataURL.split(
'data:image/png;base64,'
)[1];
generationParameters.bounding_box = boundingBox;
}
if (shouldGenerateVariations) {
@@ -105,7 +149,7 @@ export const frontendToBackendParameters = (
strength: facetoolStrength,
};
if (facetoolType === 'codeformer') {
facetoolParameters.codeformer_fidelity = codeformerFidelity
facetoolParameters.codeformer_fidelity = codeformerFidelity;
}
}

View File

@@ -0,0 +1,3 @@
export const roundDownToMultiple = (num: number, multiple: number): number => {
return Math.floor(num / multiple) * multiple;
};