feat(ui): per-layer autonegative

This commit is contained in:
psychedelicious 2024-04-19 08:37:11 +10:00 committed by Kent Keirsey
parent d3aa97ab99
commit a6e64423d9
6 changed files with 79 additions and 56 deletions

View File

@ -22,7 +22,6 @@ export const addRegionalPromptsToGraph = async (state: RootState, graph: NonNull
const { dispatch } = getStore();
// TODO: Handle non-SDXL
// const isSDXL = state.generation.model?.base === 'sdxl';
const { autoNegative } = state.regionalPrompts.present;
const layers = state.regionalPrompts.present.layers
.filter((l) => l.kind === 'promptRegionLayer') // We only want the prompt region layers
.filter((l) => l.isVisible); // Only visible layers are rendered on the canvas
@ -205,7 +204,7 @@ export const addRegionalPromptsToGraph = async (state: RootState, graph: NonNull
}
// If we are using the "invert" auto-negative setting, we need to add an additional negative conditioning node
if (autoNegative === 'invert') {
if (layer.autoNegative === 'invert') {
// We re-use the mask image, but invert it when converting to tensor
// TODO: Probably faster to invert the tensor from the earlier mask rather than read the mask image and convert...
const invertedMaskToTensorNode: S['AlphaMaskToTensorInvocation'] = {

View File

@ -0,0 +1,62 @@
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { createSelector } from '@reduxjs/toolkit';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { isParameterAutoNegative } from 'features/parameters/types/parameterSchemas';
import {
layerAutoNegativeChanged,
selectRegionalPromptsSlice,
} from 'features/regionalPrompts/store/regionalPromptsSlice';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { assert } from 'tsafe';
const options: ComboboxOption[] = [
{ label: 'Off', value: 'off' },
{ label: 'Invert', value: 'invert' },
];
type Props = {
layerId: string;
};
const useAutoNegative = (layerId: string) => {
const selectAutoNegative = useMemo(
() =>
createSelector(selectRegionalPromptsSlice, (regionalPrompts) => {
const layer = regionalPrompts.present.layers.find((l) => l.id === layerId);
assert(layer, `Layer ${layerId} not found`);
return layer.autoNegative;
}),
[layerId]
);
const autoNegative = useAppSelector(selectAutoNegative);
return autoNegative;
};
const AutoNegativeCombobox = ({ layerId }: Props) => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const autoNegative = useAutoNegative(layerId);
const onChange = useCallback<ComboboxOnChange>(
(v) => {
if (!isParameterAutoNegative(v?.value)) {
return;
}
dispatch(layerAutoNegativeChanged({ layerId, autoNegative: v.value }));
},
[dispatch, layerId]
);
const value = useMemo(() => options.find((o) => o.value === autoNegative), [autoNegative]);
return (
<FormControl flexGrow={0} gap={2} w="min-content">
<FormLabel m={0}>AutoNegative</FormLabel>
<Combobox value={value} options={options} onChange={onChange} isSearchable={false} sx={{ w: '5.2rem' }} />
</FormControl>
);
};
export default memo(AutoNegativeCombobox);

View File

@ -1,6 +1,7 @@
import { Flex, Spacer, Text } from '@invoke-ai/ui-library';
import { Flex, Spacer } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { rgbaColorToString } from 'features/canvas/util/colorToString';
import LayerAutoNegativeCombobox from 'features/regionalPrompts/components/LayerAutoNegativeCombobox';
import { LayerColorPicker } from 'features/regionalPrompts/components/LayerColorPicker';
import { LayerMenu } from 'features/regionalPrompts/components/LayerMenu';
import { LayerVisibilityToggle } from 'features/regionalPrompts/components/LayerVisibilityToggle';
@ -8,7 +9,6 @@ import { RegionalPromptsNegativePrompt } from 'features/regionalPrompts/componen
import { RegionalPromptsPositivePrompt } from 'features/regionalPrompts/components/RegionalPromptsPositivePrompt';
import { layerSelected } from 'features/regionalPrompts/store/regionalPromptsSlice';
import { memo, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
type Props = {
id: string;
@ -16,7 +16,6 @@ type Props = {
export const LayerListItem = memo(({ id }: Props) => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const selectedLayer = useAppSelector((s) => s.regionalPrompts.present.selectedLayer);
const color = useAppSelector((s) => {
const color = s.regionalPrompts.present.layers.find((l) => l.id === id)?.color;
@ -36,11 +35,7 @@ export const LayerListItem = memo(({ id }: Props) => {
<LayerColorPicker id={id} />
<LayerVisibilityToggle id={id} />
<Spacer />
{selectedLayer === id && (
<Text color="base.300" fontWeight="semibold" pe={2}>
{t('common.selected')}
</Text>
)}
<LayerAutoNegativeCombobox layerId={id} />
<LayerMenu id={id} />
</Flex>
<RegionalPromptsPositivePrompt layerId={id} />

View File

@ -1,39 +0,0 @@
import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library';
import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library';
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
import { isParameterAutoNegative } from 'features/parameters/types/parameterSchemas';
import { autoNegativeChanged } from 'features/regionalPrompts/store/regionalPromptsSlice';
import { memo, useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
const options: ComboboxOption[] = [
{ label: 'Off', value: 'off' },
{ label: 'Invert', value: 'invert' },
];
const AutoNegativeCombobox = () => {
const dispatch = useAppDispatch();
const { t } = useTranslation();
const autoNegative = useAppSelector((s) => s.regionalPrompts.present.autoNegative);
const onChange = useCallback<ComboboxOnChange>(
(v) => {
if (!isParameterAutoNegative(v?.value)) {
return;
}
dispatch(autoNegativeChanged(v.value));
},
[dispatch]
);
const value = useMemo(() => options.find((o) => o.value === autoNegative), [autoNegative]);
return (
<FormControl>
<FormLabel>Negative Mode</FormLabel>
<Combobox value={value} options={options} onChange={onChange} isSearchable={false} />
</FormControl>
);
};
export default memo(AutoNegativeCombobox);

View File

@ -6,7 +6,6 @@ import { AddLayerButton } from 'features/regionalPrompts/components/AddLayerButt
import { BrushSize } from 'features/regionalPrompts/components/BrushSize';
import { DeleteAllLayersButton } from 'features/regionalPrompts/components/DeleteAllLayersButton';
import { LayerListItem } from 'features/regionalPrompts/components/LayerListItem';
import AutoNegativeCombobox from 'features/regionalPrompts/components/NegativeModeCombobox';
import { PromptLayerOpacity } from 'features/regionalPrompts/components/PromptLayerOpacity';
import { StageComponent } from 'features/regionalPrompts/components/StageComponent';
import { ToolChooser } from 'features/regionalPrompts/components/ToolChooser';
@ -39,7 +38,6 @@ export const RegionalPromptsEditor = memo(() => {
</Flex>
<BrushSize />
<PromptLayerOpacity />
<AutoNegativeCombobox />
{layerIdsReversed.map((id) => (
<LayerListItem key={id} id={id} />
))}

View File

@ -56,6 +56,7 @@ type PromptRegionLayer = LayerBase & {
positivePrompt: string;
negativePrompt: string;
color: RgbColor;
autoNegative: ParameterAutoNegative;
};
export type Layer = PromptRegionLayer;
@ -67,7 +68,6 @@ type RegionalPromptsState = {
layers: PromptRegionLayer[];
brushSize: number;
promptLayerOpacity: number;
autoNegative: ParameterAutoNegative;
};
export const initialRegionalPromptsState: RegionalPromptsState = {
@ -77,7 +77,6 @@ export const initialRegionalPromptsState: RegionalPromptsState = {
brushSize: 40,
layers: [],
promptLayerOpacity: 0.5, // This currently doesn't work
autoNegative: 'off',
};
const isLine = (obj: LayerObject): obj is LineObject => obj.kind === 'line';
@ -99,6 +98,7 @@ export const regionalPromptsSlice = createSlice({
color: action.meta.color,
x: 0,
y: 0,
autoNegative: 'off',
};
state.layers.push(layer);
state.selectedLayer = layer.id;
@ -233,8 +233,16 @@ export const regionalPromptsSlice = createSlice({
promptLayerOpacityChanged: (state, action: PayloadAction<number>) => {
state.promptLayerOpacity = action.payload;
},
autoNegativeChanged: (state, action: PayloadAction<ParameterAutoNegative>) => {
state.autoNegative = action.payload;
layerAutoNegativeChanged: (
state,
action: PayloadAction<{ layerId: string; autoNegative: ParameterAutoNegative }>
) => {
const { layerId, autoNegative } = action.payload;
const layer = state.layers.find((l) => l.id === layerId);
if (!layer || layer.kind !== 'promptRegionLayer') {
return;
}
layer.autoNegative = autoNegative;
},
lineFinished: (state) => {
console.log('lineFinished');
@ -288,7 +296,7 @@ export const {
layerBboxChanged,
promptLayerOpacityChanged,
allLayersDeleted,
autoNegativeChanged,
layerAutoNegativeChanged,
} = regionalPromptsSlice.actions;
export const selectRegionalPromptsSlice = (state: RootState) => state.regionalPrompts;