mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): per-layer autonegative
This commit is contained in:
parent
d3aa97ab99
commit
a6e64423d9
@ -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'] = {
|
||||
|
@ -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);
|
@ -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} />
|
||||
|
@ -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);
|
@ -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} />
|
||||
))}
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user