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;