mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): make control image opacity filter toggleable
This commit is contained in:
parent
1212698059
commit
8a791d4f16
@ -1541,6 +1541,7 @@
|
|||||||
"globalControlAdapter": "Global $t(controlnet.controlAdapter_one)",
|
"globalControlAdapter": "Global $t(controlnet.controlAdapter_one)",
|
||||||
"globalControlAdapterLayer": "Global $t(controlnet.controlAdapter_one) $t(unifiedCanvas.layer)",
|
"globalControlAdapterLayer": "Global $t(controlnet.controlAdapter_one) $t(unifiedCanvas.layer)",
|
||||||
"globalIPAdapter": "Global $t(common.ipAdapter)",
|
"globalIPAdapter": "Global $t(common.ipAdapter)",
|
||||||
"globalIPAdapterLayer": "Global $t(common.ipAdapter) $t(unifiedCanvas.layer)"
|
"globalIPAdapterLayer": "Global $t(common.ipAdapter) $t(unifiedCanvas.layer)",
|
||||||
|
"opacityFilter": "Opacity Filter"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,10 +10,12 @@ import {
|
|||||||
PopoverBody,
|
PopoverBody,
|
||||||
PopoverContent,
|
PopoverContent,
|
||||||
PopoverTrigger,
|
PopoverTrigger,
|
||||||
|
Switch,
|
||||||
} from '@invoke-ai/ui-library';
|
} from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch } from 'app/store/storeHooks';
|
import { useAppDispatch } from 'app/store/storeHooks';
|
||||||
import { useLayerOpacity } from 'features/controlLayers/hooks/layerStateHooks';
|
import { useLayerOpacity } from 'features/controlLayers/hooks/layerStateHooks';
|
||||||
import { layerOpacityChanged } from 'features/controlLayers/store/controlLayersSlice';
|
import { isFilterEnabledChanged, layerOpacityChanged } from 'features/controlLayers/store/controlLayersSlice';
|
||||||
|
import type { ChangeEvent } from 'react';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiDropHalfFill } from 'react-icons/pi';
|
import { PiDropHalfFill } from 'react-icons/pi';
|
||||||
@ -28,13 +30,19 @@ const formatPct = (v: number | string) => `${v} %`;
|
|||||||
const CALayerOpacity = ({ layerId }: Props) => {
|
const CALayerOpacity = ({ layerId }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const dispatch = useAppDispatch();
|
const dispatch = useAppDispatch();
|
||||||
const opacity = useLayerOpacity(layerId);
|
const { opacity, isFilterEnabled } = useLayerOpacity(layerId);
|
||||||
const onChange = useCallback(
|
const onChangeOpacity = useCallback(
|
||||||
(v: number) => {
|
(v: number) => {
|
||||||
dispatch(layerOpacityChanged({ layerId, opacity: v / 100 }));
|
dispatch(layerOpacityChanged({ layerId, opacity: v / 100 }));
|
||||||
},
|
},
|
||||||
[dispatch, layerId]
|
[dispatch, layerId]
|
||||||
);
|
);
|
||||||
|
const onChangeFilter = useCallback(
|
||||||
|
(e: ChangeEvent<HTMLInputElement>) => {
|
||||||
|
dispatch(isFilterEnabledChanged({ layerId, isFilterEnabled: e.target.checked }));
|
||||||
|
},
|
||||||
|
[dispatch, layerId]
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<Popover isLazy>
|
<Popover isLazy>
|
||||||
<PopoverTrigger>
|
<PopoverTrigger>
|
||||||
@ -49,7 +57,13 @@ const CALayerOpacity = ({ layerId }: Props) => {
|
|||||||
<PopoverArrow />
|
<PopoverArrow />
|
||||||
<PopoverBody>
|
<PopoverBody>
|
||||||
<Flex direction="column" gap={2}>
|
<Flex direction="column" gap={2}>
|
||||||
<FormControl orientation="horizontal" minW={96}>
|
<FormControl orientation="horizontal" w="full">
|
||||||
|
<FormLabel m={0} flexGrow={1} cursor="pointer">
|
||||||
|
{t('controlLayers.opacityFilter')}
|
||||||
|
</FormLabel>
|
||||||
|
<Switch isChecked={isFilterEnabled} onChange={onChangeFilter} />
|
||||||
|
</FormControl>
|
||||||
|
<FormControl orientation="horizontal">
|
||||||
<FormLabel m={0}>{t('controlLayers.opacity')}</FormLabel>
|
<FormLabel m={0}>{t('controlLayers.opacity')}</FormLabel>
|
||||||
<CompositeSlider
|
<CompositeSlider
|
||||||
min={0}
|
min={0}
|
||||||
@ -57,8 +71,9 @@ const CALayerOpacity = ({ layerId }: Props) => {
|
|||||||
step={1}
|
step={1}
|
||||||
value={opacity}
|
value={opacity}
|
||||||
defaultValue={100}
|
defaultValue={100}
|
||||||
onChange={onChange}
|
onChange={onChangeOpacity}
|
||||||
marks={marks}
|
marks={marks}
|
||||||
|
w={48}
|
||||||
/>
|
/>
|
||||||
<CompositeNumberInput
|
<CompositeNumberInput
|
||||||
min={0}
|
min={0}
|
||||||
@ -66,8 +81,8 @@ const CALayerOpacity = ({ layerId }: Props) => {
|
|||||||
step={1}
|
step={1}
|
||||||
value={opacity}
|
value={opacity}
|
||||||
defaultValue={100}
|
defaultValue={100}
|
||||||
onChange={onChange}
|
onChange={onChangeOpacity}
|
||||||
minW={24}
|
w={24}
|
||||||
format={formatPct}
|
format={formatPct}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
@ -72,7 +72,7 @@ export const useLayerOpacity = (layerId: string) => {
|
|||||||
createSelector(selectControlLayersSlice, (controlLayers) => {
|
createSelector(selectControlLayersSlice, (controlLayers) => {
|
||||||
const layer = controlLayers.present.layers.filter(isControlAdapterLayer).find((l) => l.id === layerId);
|
const layer = controlLayers.present.layers.filter(isControlAdapterLayer).find((l) => l.id === layerId);
|
||||||
assert(layer, `Layer ${layerId} not found`);
|
assert(layer, `Layer ${layerId} not found`);
|
||||||
return Math.round(layer.opacity * 100);
|
return { opacity: Math.round(layer.opacity * 100), isFilterEnabled: layer.isFilterEnabled };
|
||||||
}),
|
}),
|
||||||
[layerId]
|
[layerId]
|
||||||
);
|
);
|
||||||
|
@ -141,6 +141,7 @@ export const controlLayersSlice = createSlice({
|
|||||||
imageName: null,
|
imageName: null,
|
||||||
opacity: 1,
|
opacity: 1,
|
||||||
isSelected: true,
|
isSelected: true,
|
||||||
|
isFilterEnabled: true,
|
||||||
};
|
};
|
||||||
state.layers.push(layer);
|
state.layers.push(layer);
|
||||||
state.selectedLayerId = layer.id;
|
state.selectedLayerId = layer.id;
|
||||||
@ -243,6 +244,19 @@ export const controlLayersSlice = createSlice({
|
|||||||
},
|
},
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
|
//#region CA Layers
|
||||||
|
isFilterEnabledChanged: (
|
||||||
|
state,
|
||||||
|
action: PayloadAction<{ layerId: string; isFilterEnabled: boolean }>
|
||||||
|
) => {
|
||||||
|
const { layerId, isFilterEnabled } = action.payload;
|
||||||
|
const layer = state.layers.filter(isControlAdapterLayer).find((l) => l.id === layerId);
|
||||||
|
if (layer) {
|
||||||
|
layer.isFilterEnabled = isFilterEnabled;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//#endregion
|
||||||
|
|
||||||
//#region Mask Layers
|
//#region Mask Layers
|
||||||
maskLayerPositivePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => {
|
maskLayerPositivePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => {
|
||||||
const { layerId, prompt } = action.payload;
|
const { layerId, prompt } = action.payload;
|
||||||
@ -522,6 +536,8 @@ export const {
|
|||||||
ipAdapterLayerAdded,
|
ipAdapterLayerAdded,
|
||||||
controlAdapterLayerAdded,
|
controlAdapterLayerAdded,
|
||||||
layerOpacityChanged,
|
layerOpacityChanged,
|
||||||
|
// CA layer actions
|
||||||
|
isFilterEnabledChanged,
|
||||||
// Mask layer actions
|
// Mask layer actions
|
||||||
maskLayerLineAdded,
|
maskLayerLineAdded,
|
||||||
maskLayerPointsAdded,
|
maskLayerPointsAdded,
|
||||||
|
@ -50,6 +50,7 @@ export type ControlAdapterLayer = RenderableLayerBase & {
|
|||||||
controlNetId: string;
|
controlNetId: string;
|
||||||
imageName: string | null;
|
imageName: string | null;
|
||||||
opacity: number;
|
opacity: number;
|
||||||
|
isFilterEnabled: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type IPAdapterLayer = LayerBase & {
|
export type IPAdapterLayer = LayerBase & {
|
||||||
|
@ -421,7 +421,6 @@ const createControlNetLayerImage = (konvaLayer: Konva.Layer, image: HTMLImageEle
|
|||||||
const konvaImage = new Konva.Image({
|
const konvaImage = new Konva.Image({
|
||||||
name: CONTROLNET_LAYER_IMAGE_NAME,
|
name: CONTROLNET_LAYER_IMAGE_NAME,
|
||||||
image,
|
image,
|
||||||
filters: [LightnessToAlphaFilter],
|
|
||||||
});
|
});
|
||||||
konvaLayer.add(konvaImage);
|
konvaLayer.add(konvaImage);
|
||||||
return konvaImage;
|
return konvaImage;
|
||||||
@ -469,10 +468,12 @@ const updateControlNetLayerImageAttrs = (
|
|||||||
let needsCache = false;
|
let needsCache = false;
|
||||||
const newWidth = stage.width() / stage.scaleX();
|
const newWidth = stage.width() / stage.scaleX();
|
||||||
const newHeight = stage.height() / stage.scaleY();
|
const newHeight = stage.height() / stage.scaleY();
|
||||||
|
const hasFilter = konvaImage.filters() !== null && konvaImage.filters().length > 0;
|
||||||
if (
|
if (
|
||||||
konvaImage.width() !== newWidth ||
|
konvaImage.width() !== newWidth ||
|
||||||
konvaImage.height() !== newHeight ||
|
konvaImage.height() !== newHeight ||
|
||||||
konvaImage.visible() !== reduxLayer.isEnabled
|
konvaImage.visible() !== reduxLayer.isEnabled ||
|
||||||
|
hasFilter !== reduxLayer.isFilterEnabled
|
||||||
) {
|
) {
|
||||||
konvaImage.setAttrs({
|
konvaImage.setAttrs({
|
||||||
opacity: reduxLayer.opacity,
|
opacity: reduxLayer.opacity,
|
||||||
@ -481,6 +482,7 @@ const updateControlNetLayerImageAttrs = (
|
|||||||
width: stage.width() / stage.scaleX(),
|
width: stage.width() / stage.scaleX(),
|
||||||
height: stage.height() / stage.scaleY(),
|
height: stage.height() / stage.scaleY(),
|
||||||
visible: reduxLayer.isEnabled,
|
visible: reduxLayer.isEnabled,
|
||||||
|
filters: reduxLayer.isFilterEnabled ? [LightnessToAlphaFilter] : [],
|
||||||
});
|
});
|
||||||
needsCache = true;
|
needsCache = true;
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user