diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 7a8c3f2f31..aab9b77073 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -1699,6 +1699,8 @@ "filter": "Filter", "convertToControlLayer": "Convert to Control Layer", "convertToRasterLayer": "Convert to Raster Layer", + "enableTransparencyEffect": "Enable Transparency Effect", + "disableTransparencyEffect": "Disable Transparency Effect", "fill": { "fillStyle": "Fill Style", "solid": "Solid", diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerMenuItems.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerMenuItems.tsx index 49f2558906..1a98c994fd 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerMenuItems.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerMenuItems.tsx @@ -4,6 +4,7 @@ import { CanvasEntityMenuItemsDelete } from 'features/controlLayers/components/c import { CanvasEntityMenuItemsFilter } from 'features/controlLayers/components/common/CanvasEntityMenuItemsFilter'; import { CanvasEntityMenuItemsReset } from 'features/controlLayers/components/common/CanvasEntityMenuItemsReset'; import { ControlLayerMenuItemsControlToRaster } from 'features/controlLayers/components/ControlLayer/ControlLayerMenuItemsControlToRaster'; +import { ControlLayerMenuItemsTransparencyEffect } from 'features/controlLayers/components/ControlLayer/ControlLayerMenuItemsTransparencyEffect'; import { memo } from 'react'; export const ControlLayerMenuItems = memo(() => { @@ -11,6 +12,7 @@ export const ControlLayerMenuItems = memo(() => { <> + diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerMenuItemsTransparencyEffect.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerMenuItemsTransparencyEffect.tsx new file mode 100644 index 0000000000..3fdb2947b7 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayer/ControlLayerMenuItemsTransparencyEffect.tsx @@ -0,0 +1,40 @@ +import { MenuItem } from '@invoke-ai/ui-library'; +import { createSelector } from '@reduxjs/toolkit'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; +import { + controlLayerWithTransparencyEffectToggled, + selectCanvasV2Slice, +} from 'features/controlLayers/store/canvasV2Slice'; +import { selectControlLayerOrThrow } from 'features/controlLayers/store/controlLayersReducers'; +import { memo, useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; +import { PiDropHalfBold } from 'react-icons/pi'; + +export const ControlLayerMenuItemsTransparencyEffect = memo(() => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const entityIdentifier = useEntityIdentifierContext(); + const selectWithTransparencyEffect = useMemo( + () => + createSelector(selectCanvasV2Slice, (canvasV2) => { + const entity = selectControlLayerOrThrow(canvasV2, entityIdentifier.id); + return entity.withTransparencyEffect; + }), + [entityIdentifier.id] + ); + const withTransparencyEffect = useAppSelector(selectWithTransparencyEffect); + const onToggle = useCallback(() => { + dispatch(controlLayerWithTransparencyEffectToggled({ id: entityIdentifier.id })); + }, [dispatch, entityIdentifier]); + + return ( + }> + {withTransparencyEffect + ? t('controlLayers.disableTransparencyEffect') + : t('controlLayers.enableTransparencyEffect')} + + ); +}); + +ControlLayerMenuItemsTransparencyEffect.displayName = 'ControlLayerMenuItemsTransparencyEffect'; diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayerAdapter.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayerAdapter.ts index 5d09fbbaa4..22318efecd 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayerAdapter.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasLayerAdapter.ts @@ -100,6 +100,12 @@ export class CanvasLayerAdapter { if (this.isFirstRender || opacity !== this.state.opacity) { this.renderer.updateOpacity(opacity); } + + if (state.type === 'control_layer' && this.state.type === 'control_layer') { + if (this.isFirstRender || state.withTransparencyEffect !== this.state.withTransparencyEffect) { + this.renderer.updateTransparencyEffect(state.withTransparencyEffect); + } + } // this.transformer.syncInteractionState(); if (this.isFirstRender) { @@ -129,12 +135,6 @@ export class CanvasLayerAdapter { } }; - updateOpacity = (arg?: { opacity: number }) => { - this.log.trace('Updating opacity'); - const opacity = get(arg, 'opacity', this.state.opacity); - this.renderer.konva.objectGroup.opacity(opacity); - }; - repr = () => { return { id: this.id, diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectRenderer.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectRenderer.ts index 37cfe1c0e6..4f4f9be875 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectRenderer.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectRenderer.ts @@ -7,6 +7,7 @@ import type { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLaye import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager'; import type { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter'; import { CanvasRectRenderer } from 'features/controlLayers/konva/CanvasRect'; +import { LightnessToAlphaFilter } from 'features/controlLayers/konva/filters'; import { getPatternSVG } from 'features/controlLayers/konva/patterns/getPatternSVG'; import { getPrefixedId, konvaNodeToBlob, konvaNodeToImageData, previewBlob } from 'features/controlLayers/konva/util'; import type { @@ -203,6 +204,11 @@ export class CanvasObjectRenderer { } }; + updateTransparencyEffect = (withTransparencyEffect: boolean) => { + const filters = withTransparencyEffect ? [LightnessToAlphaFilter] : []; + this.konva.objectGroup.filters(filters); + }; + updateCompositingRectFill = (fill: Fill) => { this.log.trace('Updating compositing rect fill'); assert(this.konva.compositing, 'Missing compositing rect'); diff --git a/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts b/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts index 588932db91..1027b05232 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts @@ -584,6 +584,7 @@ export const { controlLayerControlModeChanged, controlLayerWeightChanged, controlLayerBeginEndStepPctChanged, + controlLayerWithTransparencyEffectToggled, // IP Adapters ipaAdded, ipaRecalled, diff --git a/invokeai/frontend/web/src/features/controlLayers/store/controlLayersReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/controlLayersReducers.ts index 6133f5f790..31628f4e71 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/controlLayersReducers.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/controlLayersReducers.ts @@ -36,6 +36,7 @@ export const controlLayersReducers = { name: null, type: 'control_layer', isEnabled: true, + withTransparencyEffect: true, objects: [], opacity: 1, position: { x: 0, y: 0 }, @@ -70,7 +71,7 @@ export const controlLayersReducers = { // Convert the raster layer to control layer const rasterLayerState: CanvasRasterLayerState = { - ...omit(deepClone(layer), ['type', 'controlAdapter']), + ...omit(deepClone(layer), ['type', 'controlAdapter', 'withTransparencyEffect']), id: newId, type: 'raster_layer', }; @@ -151,4 +152,12 @@ export const controlLayersReducers = { } layer.controlAdapter.beginEndStepPct = beginEndStepPct; }, + controlLayerWithTransparencyEffectToggled: (state, action: PayloadAction<{ id: string }>) => { + const { id } = action.payload; + const layer = selectControlLayer(state, id); + if (!layer) { + return; + } + layer.withTransparencyEffect = !layer.withTransparencyEffect; + }, } satisfies SliceCaseReducers; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/rasterLayersReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/rasterLayersReducers.ts index df6326dc9f..1fcbb7cd92 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/rasterLayersReducers.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/rasterLayersReducers.ts @@ -89,6 +89,7 @@ export const rasterLayersReducers = { id: newId, type: 'control_layer', controlAdapter, + withTransparencyEffect: true, }; // Remove the raster layer diff --git a/invokeai/frontend/web/src/features/controlLayers/store/types.ts b/invokeai/frontend/web/src/features/controlLayers/store/types.ts index 5adba63711..bc59752e3d 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/types.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/types.ts @@ -559,14 +559,14 @@ const zCanvasRectState = z.object({ }); export type CanvasRectState = z.infer; -const zFilter = z.enum(['LightnessToAlphaFilter']); -export type Filter = z.infer; +const zLayerEffect = z.enum(['LightnessToAlphaFilter']); +export type LayerEffect = z.infer; const zCanvasImageState = z.object({ id: zId, type: z.literal('image'), image: zImageWithDims, - filters: z.array(zFilter), + filters: z.array(zLayerEffect), }); export type CanvasImageState = z.infer; @@ -699,7 +699,7 @@ const zCanvasControlAdapterStateBase = z.object({ isEnabled: z.boolean(), position: zCoordinate, opacity: zOpacity, - filters: z.array(zFilter), + filters: z.array(zLayerEffect), weight: z.number().gte(-1).lte(2), imageObject: zCanvasImageState.nullable(), processedImageObject: zCanvasImageState.nullable(), @@ -755,6 +755,7 @@ export type CanvasRasterLayerState = z.infer; export const zCanvasControlLayerState = zCanvasRasterLayerState.extend({ type: z.literal('control_layer'), + withTransparencyEffect: z.boolean(), controlAdapter: z.discriminatedUnion('type', [zControlNetConfig, zT2IAdapterConfig]), }); export type CanvasControlLayerState = z.infer;