feat(ui): make control image opacity filter toggleable

This commit is contained in:
psychedelicious 2024-04-30 18:50:29 +10:00 committed by Kent Keirsey
parent 1212698059
commit 8a791d4f16
6 changed files with 46 additions and 11 deletions

View File

@ -1541,6 +1541,7 @@
"globalControlAdapter": "Global $t(controlnet.controlAdapter_one)",
"globalControlAdapterLayer": "Global $t(controlnet.controlAdapter_one) $t(unifiedCanvas.layer)",
"globalIPAdapter": "Global $t(common.ipAdapter)",
"globalIPAdapterLayer": "Global $t(common.ipAdapter) $t(unifiedCanvas.layer)"
"globalIPAdapterLayer": "Global $t(common.ipAdapter) $t(unifiedCanvas.layer)",
"opacityFilter": "Opacity Filter"
}
}

View File

@ -10,10 +10,12 @@ import {
PopoverBody,
PopoverContent,
PopoverTrigger,
Switch,
} from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
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 { useTranslation } from 'react-i18next';
import { PiDropHalfFill } from 'react-icons/pi';
@ -28,13 +30,19 @@ const formatPct = (v: number | string) => `${v} %`;
const CALayerOpacity = ({ layerId }: Props) => {
const { t } = useTranslation();
const dispatch = useAppDispatch();
const opacity = useLayerOpacity(layerId);
const onChange = useCallback(
const { opacity, isFilterEnabled } = useLayerOpacity(layerId);
const onChangeOpacity = useCallback(
(v: number) => {
dispatch(layerOpacityChanged({ layerId, opacity: v / 100 }));
},
[dispatch, layerId]
);
const onChangeFilter = useCallback(
(e: ChangeEvent<HTMLInputElement>) => {
dispatch(isFilterEnabledChanged({ layerId, isFilterEnabled: e.target.checked }));
},
[dispatch, layerId]
);
return (
<Popover isLazy>
<PopoverTrigger>
@ -49,7 +57,13 @@ const CALayerOpacity = ({ layerId }: Props) => {
<PopoverArrow />
<PopoverBody>
<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>
<CompositeSlider
min={0}
@ -57,8 +71,9 @@ const CALayerOpacity = ({ layerId }: Props) => {
step={1}
value={opacity}
defaultValue={100}
onChange={onChange}
onChange={onChangeOpacity}
marks={marks}
w={48}
/>
<CompositeNumberInput
min={0}
@ -66,8 +81,8 @@ const CALayerOpacity = ({ layerId }: Props) => {
step={1}
value={opacity}
defaultValue={100}
onChange={onChange}
minW={24}
onChange={onChangeOpacity}
w={24}
format={formatPct}
/>
</FormControl>

View File

@ -72,7 +72,7 @@ export const useLayerOpacity = (layerId: string) => {
createSelector(selectControlLayersSlice, (controlLayers) => {
const layer = controlLayers.present.layers.filter(isControlAdapterLayer).find((l) => l.id === layerId);
assert(layer, `Layer ${layerId} not found`);
return Math.round(layer.opacity * 100);
return { opacity: Math.round(layer.opacity * 100), isFilterEnabled: layer.isFilterEnabled };
}),
[layerId]
);

View File

@ -141,6 +141,7 @@ export const controlLayersSlice = createSlice({
imageName: null,
opacity: 1,
isSelected: true,
isFilterEnabled: true,
};
state.layers.push(layer);
state.selectedLayerId = layer.id;
@ -243,6 +244,19 @@ export const controlLayersSlice = createSlice({
},
//#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
maskLayerPositivePromptChanged: (state, action: PayloadAction<{ layerId: string; prompt: string | null }>) => {
const { layerId, prompt } = action.payload;
@ -522,6 +536,8 @@ export const {
ipAdapterLayerAdded,
controlAdapterLayerAdded,
layerOpacityChanged,
// CA layer actions
isFilterEnabledChanged,
// Mask layer actions
maskLayerLineAdded,
maskLayerPointsAdded,

View File

@ -50,6 +50,7 @@ export type ControlAdapterLayer = RenderableLayerBase & {
controlNetId: string;
imageName: string | null;
opacity: number;
isFilterEnabled: boolean;
};
export type IPAdapterLayer = LayerBase & {

View File

@ -421,7 +421,6 @@ const createControlNetLayerImage = (konvaLayer: Konva.Layer, image: HTMLImageEle
const konvaImage = new Konva.Image({
name: CONTROLNET_LAYER_IMAGE_NAME,
image,
filters: [LightnessToAlphaFilter],
});
konvaLayer.add(konvaImage);
return konvaImage;
@ -469,10 +468,12 @@ const updateControlNetLayerImageAttrs = (
let needsCache = false;
const newWidth = stage.width() / stage.scaleX();
const newHeight = stage.height() / stage.scaleY();
const hasFilter = konvaImage.filters() !== null && konvaImage.filters().length > 0;
if (
konvaImage.width() !== newWidth ||
konvaImage.height() !== newHeight ||
konvaImage.visible() !== reduxLayer.isEnabled
konvaImage.visible() !== reduxLayer.isEnabled ||
hasFilter !== reduxLayer.isFilterEnabled
) {
konvaImage.setAttrs({
opacity: reduxLayer.opacity,
@ -481,6 +482,7 @@ const updateControlNetLayerImageAttrs = (
width: stage.width() / stage.scaleX(),
height: stage.height() / stage.scaleY(),
visible: reduxLayer.isEnabled,
filters: reduxLayer.isFilterEnabled ? [LightnessToAlphaFilter] : [],
});
needsCache = true;
}