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();
|
const { dispatch } = getStore();
|
||||||
// TODO: Handle non-SDXL
|
// TODO: Handle non-SDXL
|
||||||
// const isSDXL = state.generation.model?.base === 'sdxl';
|
// const isSDXL = state.generation.model?.base === 'sdxl';
|
||||||
const { autoNegative } = state.regionalPrompts.present;
|
|
||||||
const layers = state.regionalPrompts.present.layers
|
const layers = state.regionalPrompts.present.layers
|
||||||
.filter((l) => l.kind === 'promptRegionLayer') // We only want the prompt region 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
|
.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 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
|
// 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...
|
// TODO: Probably faster to invert the tensor from the earlier mask rather than read the mask image and convert...
|
||||||
const invertedMaskToTensorNode: S['AlphaMaskToTensorInvocation'] = {
|
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 { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
import { rgbaColorToString } from 'features/canvas/util/colorToString';
|
||||||
|
import LayerAutoNegativeCombobox from 'features/regionalPrompts/components/LayerAutoNegativeCombobox';
|
||||||
import { LayerColorPicker } from 'features/regionalPrompts/components/LayerColorPicker';
|
import { LayerColorPicker } from 'features/regionalPrompts/components/LayerColorPicker';
|
||||||
import { LayerMenu } from 'features/regionalPrompts/components/LayerMenu';
|
import { LayerMenu } from 'features/regionalPrompts/components/LayerMenu';
|
||||||
import { LayerVisibilityToggle } from 'features/regionalPrompts/components/LayerVisibilityToggle';
|
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 { RegionalPromptsPositivePrompt } from 'features/regionalPrompts/components/RegionalPromptsPositivePrompt';
|
||||||
import { layerSelected } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
import { layerSelected } from 'features/regionalPrompts/store/regionalPromptsSlice';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -16,7 +16,6 @@ type Props = {
|
|||||||
|
|
||||||
export const LayerListItem = memo(({ id }: Props) => {
|
export const LayerListItem = memo(({ id }: Props) => {
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const { t } = useTranslation();
|
|
||||||
const selectedLayer = useAppSelector((s) => s.regionalPrompts.present.selectedLayer);
|
const selectedLayer = useAppSelector((s) => s.regionalPrompts.present.selectedLayer);
|
||||||
const color = useAppSelector((s) => {
|
const color = useAppSelector((s) => {
|
||||||
const color = s.regionalPrompts.present.layers.find((l) => l.id === id)?.color;
|
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} />
|
<LayerColorPicker id={id} />
|
||||||
<LayerVisibilityToggle id={id} />
|
<LayerVisibilityToggle id={id} />
|
||||||
<Spacer />
|
<Spacer />
|
||||||
{selectedLayer === id && (
|
<LayerAutoNegativeCombobox layerId={id} />
|
||||||
<Text color="base.300" fontWeight="semibold" pe={2}>
|
|
||||||
{t('common.selected')}
|
|
||||||
</Text>
|
|
||||||
)}
|
|
||||||
<LayerMenu id={id} />
|
<LayerMenu id={id} />
|
||||||
</Flex>
|
</Flex>
|
||||||
<RegionalPromptsPositivePrompt layerId={id} />
|
<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 { BrushSize } from 'features/regionalPrompts/components/BrushSize';
|
||||||
import { DeleteAllLayersButton } from 'features/regionalPrompts/components/DeleteAllLayersButton';
|
import { DeleteAllLayersButton } from 'features/regionalPrompts/components/DeleteAllLayersButton';
|
||||||
import { LayerListItem } from 'features/regionalPrompts/components/LayerListItem';
|
import { LayerListItem } from 'features/regionalPrompts/components/LayerListItem';
|
||||||
import AutoNegativeCombobox from 'features/regionalPrompts/components/NegativeModeCombobox';
|
|
||||||
import { PromptLayerOpacity } from 'features/regionalPrompts/components/PromptLayerOpacity';
|
import { PromptLayerOpacity } from 'features/regionalPrompts/components/PromptLayerOpacity';
|
||||||
import { StageComponent } from 'features/regionalPrompts/components/StageComponent';
|
import { StageComponent } from 'features/regionalPrompts/components/StageComponent';
|
||||||
import { ToolChooser } from 'features/regionalPrompts/components/ToolChooser';
|
import { ToolChooser } from 'features/regionalPrompts/components/ToolChooser';
|
||||||
@ -39,7 +38,6 @@ export const RegionalPromptsEditor = memo(() => {
|
|||||||
</Flex>
|
</Flex>
|
||||||
<BrushSize />
|
<BrushSize />
|
||||||
<PromptLayerOpacity />
|
<PromptLayerOpacity />
|
||||||
<AutoNegativeCombobox />
|
|
||||||
{layerIdsReversed.map((id) => (
|
{layerIdsReversed.map((id) => (
|
||||||
<LayerListItem key={id} id={id} />
|
<LayerListItem key={id} id={id} />
|
||||||
))}
|
))}
|
||||||
|
@ -56,6 +56,7 @@ type PromptRegionLayer = LayerBase & {
|
|||||||
positivePrompt: string;
|
positivePrompt: string;
|
||||||
negativePrompt: string;
|
negativePrompt: string;
|
||||||
color: RgbColor;
|
color: RgbColor;
|
||||||
|
autoNegative: ParameterAutoNegative;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Layer = PromptRegionLayer;
|
export type Layer = PromptRegionLayer;
|
||||||
@ -67,7 +68,6 @@ type RegionalPromptsState = {
|
|||||||
layers: PromptRegionLayer[];
|
layers: PromptRegionLayer[];
|
||||||
brushSize: number;
|
brushSize: number;
|
||||||
promptLayerOpacity: number;
|
promptLayerOpacity: number;
|
||||||
autoNegative: ParameterAutoNegative;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const initialRegionalPromptsState: RegionalPromptsState = {
|
export const initialRegionalPromptsState: RegionalPromptsState = {
|
||||||
@ -77,7 +77,6 @@ export const initialRegionalPromptsState: RegionalPromptsState = {
|
|||||||
brushSize: 40,
|
brushSize: 40,
|
||||||
layers: [],
|
layers: [],
|
||||||
promptLayerOpacity: 0.5, // This currently doesn't work
|
promptLayerOpacity: 0.5, // This currently doesn't work
|
||||||
autoNegative: 'off',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const isLine = (obj: LayerObject): obj is LineObject => obj.kind === 'line';
|
const isLine = (obj: LayerObject): obj is LineObject => obj.kind === 'line';
|
||||||
@ -99,6 +98,7 @@ export const regionalPromptsSlice = createSlice({
|
|||||||
color: action.meta.color,
|
color: action.meta.color,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
|
autoNegative: 'off',
|
||||||
};
|
};
|
||||||
state.layers.push(layer);
|
state.layers.push(layer);
|
||||||
state.selectedLayer = layer.id;
|
state.selectedLayer = layer.id;
|
||||||
@ -233,8 +233,16 @@ export const regionalPromptsSlice = createSlice({
|
|||||||
promptLayerOpacityChanged: (state, action: PayloadAction<number>) => {
|
promptLayerOpacityChanged: (state, action: PayloadAction<number>) => {
|
||||||
state.promptLayerOpacity = action.payload;
|
state.promptLayerOpacity = action.payload;
|
||||||
},
|
},
|
||||||
autoNegativeChanged: (state, action: PayloadAction<ParameterAutoNegative>) => {
|
layerAutoNegativeChanged: (
|
||||||
state.autoNegative = action.payload;
|
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) => {
|
lineFinished: (state) => {
|
||||||
console.log('lineFinished');
|
console.log('lineFinished');
|
||||||
@ -288,7 +296,7 @@ export const {
|
|||||||
layerBboxChanged,
|
layerBboxChanged,
|
||||||
promptLayerOpacityChanged,
|
promptLayerOpacityChanged,
|
||||||
allLayersDeleted,
|
allLayersDeleted,
|
||||||
autoNegativeChanged,
|
layerAutoNegativeChanged,
|
||||||
} = regionalPromptsSlice.actions;
|
} = regionalPromptsSlice.actions;
|
||||||
|
|
||||||
export const selectRegionalPromptsSlice = (state: RootState) => state.regionalPrompts;
|
export const selectRegionalPromptsSlice = (state: RootState) => state.regionalPrompts;
|
||||||
|
Loading…
Reference in New Issue
Block a user