mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): add opacity to initial image layer
This commit is contained in:
parent
75be6814bb
commit
8b6a283eab
@ -1,5 +1,6 @@
|
|||||||
import { Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
import { Flex, Spacer, useDisclosure } from '@invoke-ai/ui-library';
|
||||||
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import IILayerOpacity from 'features/controlLayers/components/IILayer/IILayerOpacity';
|
||||||
import { InitialImagePreview } from 'features/controlLayers/components/IILayer/InitialImagePreview';
|
import { InitialImagePreview } from 'features/controlLayers/components/IILayer/InitialImagePreview';
|
||||||
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
import { LayerDeleteButton } from 'features/controlLayers/components/LayerCommon/LayerDeleteButton';
|
||||||
import { LayerMenu } from 'features/controlLayers/components/LayerCommon/LayerMenu';
|
import { LayerMenu } from 'features/controlLayers/components/LayerCommon/LayerMenu';
|
||||||
@ -60,6 +61,7 @@ export const IILayer = memo(({ layerId }: Props) => {
|
|||||||
<LayerVisibilityToggle layerId={layerId} />
|
<LayerVisibilityToggle layerId={layerId} />
|
||||||
<LayerTitle type="initial_image_layer" />
|
<LayerTitle type="initial_image_layer" />
|
||||||
<Spacer />
|
<Spacer />
|
||||||
|
<IILayerOpacity layerId={layerId} />
|
||||||
<LayerMenu layerId={layerId} />
|
<LayerMenu layerId={layerId} />
|
||||||
<LayerDeleteButton layerId={layerId} />
|
<LayerDeleteButton layerId={layerId} />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -0,0 +1,98 @@
|
|||||||
|
import {
|
||||||
|
CompositeNumberInput,
|
||||||
|
CompositeSlider,
|
||||||
|
Flex,
|
||||||
|
FormControl,
|
||||||
|
FormLabel,
|
||||||
|
IconButton,
|
||||||
|
Popover,
|
||||||
|
PopoverArrow,
|
||||||
|
PopoverBody,
|
||||||
|
PopoverContent,
|
||||||
|
PopoverTrigger,
|
||||||
|
} from '@invoke-ai/ui-library';
|
||||||
|
import { createSelector } from '@reduxjs/toolkit';
|
||||||
|
import { useAppDispatch, useAppSelector } from 'app/store/storeHooks';
|
||||||
|
import { stopPropagation } from 'common/util/stopPropagation';
|
||||||
|
import {
|
||||||
|
iiLayerOpacityChanged,
|
||||||
|
isInitialImageLayer,
|
||||||
|
selectControlLayersSlice,
|
||||||
|
} from 'features/controlLayers/store/controlLayersSlice';
|
||||||
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { PiDropHalfFill } from 'react-icons/pi';
|
||||||
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
layerId: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
const marks = [0, 25, 50, 75, 100];
|
||||||
|
const formatPct = (v: number | string) => `${v} %`;
|
||||||
|
|
||||||
|
const IILayerOpacity = ({ layerId }: Props) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const dispatch = useAppDispatch();
|
||||||
|
const selectOpacity = useMemo(
|
||||||
|
() =>
|
||||||
|
createSelector(selectControlLayersSlice, (controlLayers) => {
|
||||||
|
const layer = controlLayers.present.layers.filter(isInitialImageLayer).find((l) => l.id === layerId);
|
||||||
|
assert(layer, `Layer ${layerId} not found`);
|
||||||
|
return Math.round(layer.opacity * 100);
|
||||||
|
}),
|
||||||
|
[layerId]
|
||||||
|
);
|
||||||
|
const opacity = useAppSelector(selectOpacity);
|
||||||
|
const onChangeOpacity = useCallback(
|
||||||
|
(v: number) => {
|
||||||
|
dispatch(iiLayerOpacityChanged({ layerId, opacity: v / 100 }));
|
||||||
|
},
|
||||||
|
[dispatch, layerId]
|
||||||
|
);
|
||||||
|
return (
|
||||||
|
<Popover isLazy>
|
||||||
|
<PopoverTrigger>
|
||||||
|
<IconButton
|
||||||
|
aria-label={t('controlLayers.opacity')}
|
||||||
|
size="sm"
|
||||||
|
icon={<PiDropHalfFill size={16} />}
|
||||||
|
variant="ghost"
|
||||||
|
onDoubleClick={stopPropagation}
|
||||||
|
/>
|
||||||
|
</PopoverTrigger>
|
||||||
|
<PopoverContent>
|
||||||
|
<PopoverArrow />
|
||||||
|
<PopoverBody>
|
||||||
|
<Flex direction="column" gap={2}>
|
||||||
|
<FormControl orientation="horizontal">
|
||||||
|
<FormLabel m={0}>{t('controlLayers.opacity')}</FormLabel>
|
||||||
|
<CompositeSlider
|
||||||
|
min={0}
|
||||||
|
max={100}
|
||||||
|
step={1}
|
||||||
|
value={opacity}
|
||||||
|
defaultValue={100}
|
||||||
|
onChange={onChangeOpacity}
|
||||||
|
marks={marks}
|
||||||
|
w={48}
|
||||||
|
/>
|
||||||
|
<CompositeNumberInput
|
||||||
|
min={0}
|
||||||
|
max={100}
|
||||||
|
step={1}
|
||||||
|
value={opacity}
|
||||||
|
defaultValue={100}
|
||||||
|
onChange={onChangeOpacity}
|
||||||
|
w={24}
|
||||||
|
format={formatPct}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</Flex>
|
||||||
|
</PopoverBody>
|
||||||
|
</PopoverContent>
|
||||||
|
</Popover>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default memo(IILayerOpacity);
|
@ -631,6 +631,7 @@ export const controlLayersSlice = createSlice({
|
|||||||
const layer: InitialImageLayer = {
|
const layer: InitialImageLayer = {
|
||||||
id: layerId,
|
id: layerId,
|
||||||
type: 'initial_image_layer',
|
type: 'initial_image_layer',
|
||||||
|
opacity: 1,
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
bbox: null,
|
bbox: null,
|
||||||
@ -652,12 +653,15 @@ export const controlLayersSlice = createSlice({
|
|||||||
iiLayerImageChanged: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
|
iiLayerImageChanged: (state, action: PayloadAction<{ layerId: string; imageDTO: ImageDTO | null }>) => {
|
||||||
const { layerId, imageDTO } = action.payload;
|
const { layerId, imageDTO } = action.payload;
|
||||||
const layer = selectIILayerOrThrow(state, layerId);
|
const layer = selectIILayerOrThrow(state, layerId);
|
||||||
if (layer) {
|
|
||||||
layer.bbox = null;
|
layer.bbox = null;
|
||||||
layer.bboxNeedsUpdate = true;
|
layer.bboxNeedsUpdate = true;
|
||||||
layer.isEnabled = true;
|
layer.isEnabled = true;
|
||||||
layer.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
layer.image = imageDTO ? imageDTOToImageWithDims(imageDTO) : null;
|
||||||
}
|
},
|
||||||
|
iiLayerOpacityChanged: (state, action: PayloadAction<{ layerId: string; opacity: number }>) => {
|
||||||
|
const { layerId, opacity } = action.payload;
|
||||||
|
const layer = selectIILayerOrThrow(state, layerId);
|
||||||
|
layer.opacity = opacity;
|
||||||
},
|
},
|
||||||
//#endregion
|
//#endregion
|
||||||
|
|
||||||
@ -833,6 +837,7 @@ export const {
|
|||||||
// II Layer
|
// II Layer
|
||||||
iiLayerAdded,
|
iiLayerAdded,
|
||||||
iiLayerImageChanged,
|
iiLayerImageChanged,
|
||||||
|
iiLayerOpacityChanged,
|
||||||
// Globals
|
// Globals
|
||||||
positivePromptChanged,
|
positivePromptChanged,
|
||||||
negativePromptChanged,
|
negativePromptChanged,
|
||||||
|
@ -76,6 +76,7 @@ export type RegionalGuidanceLayer = RenderableLayerBase & {
|
|||||||
|
|
||||||
export type InitialImageLayer = RenderableLayerBase & {
|
export type InitialImageLayer = RenderableLayerBase & {
|
||||||
type: 'initial_image_layer';
|
type: 'initial_image_layer';
|
||||||
|
opacity: number;
|
||||||
image: ImageWithDims | null;
|
image: ImageWithDims | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -443,7 +443,7 @@ const updateInitialImageLayerImageAttrs = (
|
|||||||
konvaImage.visible() !== reduxLayer.isEnabled
|
konvaImage.visible() !== reduxLayer.isEnabled
|
||||||
) {
|
) {
|
||||||
konvaImage.setAttrs({
|
konvaImage.setAttrs({
|
||||||
// opacity: reduxLayer.opacity,
|
opacity: reduxLayer.opacity,
|
||||||
scaleX: 1,
|
scaleX: 1,
|
||||||
scaleY: 1,
|
scaleY: 1,
|
||||||
width: stage.width() / stage.scaleX(),
|
width: stage.width() / stage.scaleX(),
|
||||||
@ -451,9 +451,9 @@ const updateInitialImageLayerImageAttrs = (
|
|||||||
visible: reduxLayer.isEnabled,
|
visible: reduxLayer.isEnabled,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// if (konvaImage.opacity() !== reduxLayer.opacity) {
|
if (konvaImage.opacity() !== reduxLayer.opacity) {
|
||||||
// konvaImage.opacity(reduxLayer.opacity);
|
konvaImage.opacity(reduxLayer.opacity);
|
||||||
// }
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateInitialImageLayerImageSource = async (
|
const updateInitialImageLayerImageSource = async (
|
||||||
|
Loading…
x
Reference in New Issue
Block a user