diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersSettingsPopover.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersSettingsPopover.tsx index 11da129a8e..8a7eb0b98e 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersSettingsPopover.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersSettingsPopover.tsx @@ -12,7 +12,6 @@ import { } from '@invoke-ai/ui-library'; import { useStore } from '@nanostores/react'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { MaskOpacity } from 'features/controlLayers/components/MaskOpacity'; import { $canvasManager } from 'features/controlLayers/konva/CanvasManager'; import { clipToBboxChanged, @@ -64,7 +63,6 @@ const ControlLayersSettingsPopover = () => { - {t('unifiedCanvas.invertBrushSizeScrollDirection')} diff --git a/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/InpaintMaskMaskFillColorPicker.tsx b/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/InpaintMaskMaskFillColorPicker.tsx index f7d834bfbc..42951878b4 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/InpaintMaskMaskFillColorPicker.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/InpaintMask/InpaintMaskMaskFillColorPicker.tsx @@ -1,13 +1,12 @@ import { Flex, Popover, PopoverBody, PopoverContent, PopoverTrigger } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import RgbColorPicker from 'common/components/RgbColorPicker'; +import IAIColorPicker from 'common/components/IAIColorPicker'; import { rgbColorToString } from 'common/util/colorCodeTransformers'; import { stopPropagation } from 'common/util/stopPropagation'; import { MaskFillStyle } from 'features/controlLayers/components/common/MaskFillStyle'; import { imFillColorChanged, imFillStyleChanged } from 'features/controlLayers/store/canvasV2Slice'; -import type { FillStyle } from 'features/controlLayers/store/types'; +import type { FillStyle, RgbaColor } from 'features/controlLayers/store/types'; import { memo, useCallback } from 'react'; -import type { RgbColor } from 'react-colorful'; import { useTranslation } from 'react-i18next'; export const InpaintMaskMaskFillColorPicker = memo(() => { @@ -15,7 +14,7 @@ export const InpaintMaskMaskFillColorPicker = memo(() => { const dispatch = useAppDispatch(); const fill = useAppSelector((s) => s.canvasV2.inpaintMask.fill); const onChangeFillColor = useCallback( - (color: RgbColor) => { + (color: RgbaColor) => { dispatch(imFillColorChanged({ color })); }, [dispatch] @@ -44,7 +43,7 @@ export const InpaintMaskMaskFillColorPicker = memo(() => { - + diff --git a/invokeai/frontend/web/src/features/controlLayers/components/MaskOpacity.tsx b/invokeai/frontend/web/src/features/controlLayers/components/MaskOpacity.tsx deleted file mode 100644 index d3372f4fae..0000000000 --- a/invokeai/frontend/web/src/features/controlLayers/components/MaskOpacity.tsx +++ /dev/null @@ -1,49 +0,0 @@ -import { CompositeNumberInput, CompositeSlider, Flex, FormControl, FormLabel } from '@invoke-ai/ui-library'; -import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import { maskOpacityChanged } from 'features/controlLayers/store/canvasV2Slice'; -import { memo, useCallback } from 'react'; -import { useTranslation } from 'react-i18next'; - -const marks = [0, 25, 50, 75, 100]; -const formatPct = (v: number | string) => `${v} %`; - -export const MaskOpacity = memo(() => { - const dispatch = useAppDispatch(); - const { t } = useTranslation(); - const opacity = useAppSelector((s) => Math.round(s.canvasV2.settings.maskOpacity * 100)); - const onChange = useCallback( - (v: number) => { - dispatch(maskOpacityChanged(Math.max(v / 100, 0.25))); - }, - [dispatch] - ); - return ( - - {t('controlLayers.globalMaskOpacity')} - - - - - - ); -}); - -MaskOpacity.displayName = 'MaskOpacity'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceMaskFillColorPicker.tsx b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceMaskFillColorPicker.tsx index 85bd4758f6..4f1780d6f6 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceMaskFillColorPicker.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/RegionalGuidance/RegionalGuidanceMaskFillColorPicker.tsx @@ -1,15 +1,14 @@ import { Flex, Popover, PopoverBody, PopoverContent, PopoverTrigger } from '@invoke-ai/ui-library'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; -import RgbColorPicker from 'common/components/RgbColorPicker'; +import IAIColorPicker from 'common/components/IAIColorPicker'; import { rgbColorToString } from 'common/util/colorCodeTransformers'; import { stopPropagation } from 'common/util/stopPropagation'; import { MaskFillStyle } from 'features/controlLayers/components/common/MaskFillStyle'; import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; import { rgFillColorChanged, rgFillStyleChanged } from 'features/controlLayers/store/canvasV2Slice'; import { selectRegionalGuidanceEntityOrThrow } from 'features/controlLayers/store/regionsReducers'; -import type { FillStyle } from 'features/controlLayers/store/types'; +import type { FillStyle, RgbaColor } from 'features/controlLayers/store/types'; import { memo, useCallback } from 'react'; -import type { RgbColor } from 'react-colorful'; import { useTranslation } from 'react-i18next'; export const RegionalGuidanceMaskFillColorPicker = memo(() => { @@ -18,7 +17,7 @@ export const RegionalGuidanceMaskFillColorPicker = memo(() => { const dispatch = useAppDispatch(); const fill = useAppSelector((s) => selectRegionalGuidanceEntityOrThrow(s.canvasV2, entityIdentifier.id).fill); const onChangeFillColor = useCallback( - (color: RgbColor) => { + (color: RgbaColor) => { dispatch(rgFillColorChanged({ id: entityIdentifier.id, color })); }, [dispatch, entityIdentifier.id] @@ -47,7 +46,7 @@ export const RegionalGuidanceMaskFillColorPicker = memo(() => { - + diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts index 3df53ee23c..ef0e8fe781 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasManager.ts @@ -323,7 +323,6 @@ export class CanvasManager { if ( this._isFirstRender || state.regions.entities !== this._prevState.regions.entities || - state.settings.maskOpacity !== this._prevState.settings.maskOpacity || state.tool.selected !== this._prevState.tool.selected || state.selectedEntityIdentifier?.id !== this._prevState.selectedEntityIdentifier?.id ) { @@ -355,7 +354,6 @@ export class CanvasManager { if ( this._isFirstRender || state.inpaintMask !== this._prevState.inpaintMask || - state.settings.maskOpacity !== this._prevState.settings.maskOpacity || state.tool.selected !== this._prevState.tool.selected || state.selectedEntityIdentifier?.id !== this._prevState.selectedEntityIdentifier?.id ) { diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasMaskAdapter.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasMaskAdapter.ts index 550e15fa00..da70ab1fa6 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasMaskAdapter.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasMaskAdapter.ts @@ -22,7 +22,6 @@ export class CanvasMaskAdapter { log: Logger; state: CanvasInpaintMaskState | CanvasRegionalGuidanceState; - maskOpacity: number; transformer: CanvasTransformer; renderer: CanvasObjectRenderer; @@ -54,8 +53,6 @@ export class CanvasMaskAdapter { this.renderer = new CanvasObjectRenderer(this); this.transformer = new CanvasTransformer(this); - - this.maskOpacity = this.manager.stateApi.getMaskOpacity(); } /** @@ -79,14 +76,8 @@ export class CanvasMaskAdapter { isSelected: boolean; }) => { const state = get(arg, 'state', this.state); - const maskOpacity = this.manager.stateApi.getMaskOpacity(); - if ( - !this.isFirstRender && - state === this.state && - state.fill === this.state.fill && - maskOpacity === this.maskOpacity - ) { + if (!this.isFirstRender && state === this.state && state.fill === this.state.fill) { this.log.trace('State unchanged, skipping update'); return; } @@ -107,10 +98,6 @@ export class CanvasMaskAdapter { this.updateVisibility({ isEnabled }); } - if (this.isFirstRender || state.fill !== this.state.fill || maskOpacity !== this.maskOpacity) { - this.renderer.updateCompositingRect(state.fill, maskOpacity); - this.maskOpacity = maskOpacity; - } // this.transformer.syncInteractionState(); if (this.isFirstRender) { diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectRenderer.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectRenderer.ts index d0a409ab94..9fd475ffa4 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectRenderer.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasObjectRenderer.ts @@ -1,5 +1,5 @@ import type { JSONObject } from 'common/types'; -import { rgbColorToString } from 'common/util/colorCodeTransformers'; +import { rgbaColorToString } from 'common/util/colorCodeTransformers'; import { deepClone } from 'common/util/deepClone'; import { CanvasBrushLineRenderer } from 'features/controlLayers/konva/CanvasBrushLine'; import { CanvasEraserLineRenderer } from 'features/controlLayers/konva/CanvasEraserLine'; @@ -149,7 +149,7 @@ export class CanvasObjectRenderer { this.subscriptions.add( this.manager.stateApi.$stageAttrs.listen(() => { if (this.konva.compositing && this.parent.type === 'mask_adapter') { - this.updateCompositingRect(this.parent.state.fill, this.manager.stateApi.getMaskOpacity()); + this.updateCompositingRect(this.parent.state.fill); } }) ); @@ -183,14 +183,13 @@ export class CanvasObjectRenderer { return didRender; }; - updateCompositingRect = (fill: Fill, opacity: number) => { + updateCompositingRect = (fill: Fill) => { this.log.trace('Updating compositing rect'); assert(this.konva.compositing, 'Missing compositing rect'); const { x, y, width, height, scale } = this.manager.stateApi.$stageAttrs.get(); - console.log('stageAttrs', this.manager.stateApi.$stageAttrs.get()); + const attrs: RectConfig = { - opacity, x: -x / scale, y: -y / scale, width: width / scale, @@ -198,7 +197,7 @@ export class CanvasObjectRenderer { }; if (fill.style === 'solid') { - attrs.fill = rgbColorToString(fill.color); + attrs.fill = rgbaColorToString(fill.color); attrs.fillPriority = 'color'; this.konva.compositing.rect.setAttrs(attrs); } else { diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApi.ts b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApi.ts index e96e6c3ea0..262e28dce5 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApi.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/CanvasStateApi.ts @@ -171,9 +171,6 @@ export class CanvasStateApi { getInpaintMaskState = () => { return this.getState().inpaintMask; }; - getMaskOpacity = () => { - return this.getState().settings.maskOpacity; - }; getSession = () => { return this.getState().session; }; @@ -232,26 +229,23 @@ export class CanvasStateApi { let currentFill: RgbaColor = state.tool.fill; const selectedEntity = this.getSelectedEntity(); if (selectedEntity) { - // These two entity types use a compositing rect for opacity. Their fill is always white. + // These two entity types use a compositing rect for opacity. Their fill is always a solid color. if (selectedEntity.state.type === 'regional_guidance' || selectedEntity.state.type === 'inpaint_mask') { currentFill = RGBA_RED; - // currentFill = RGBA_WHITE; } } return currentFill; }; - getBrushPreviewFill = () => { - const state = this.getState(); - let currentFill: RgbaColor = state.tool.fill; + getBrushPreviewFill = (): RgbaColor => { const selectedEntity = this.getSelectedEntity(); - if (selectedEntity) { + if (selectedEntity?.state.type === 'regional_guidance' || selectedEntity?.state.type === 'inpaint_mask') { // The brush should use the mask opacity for these entity types - if (selectedEntity.state.type === 'regional_guidance' || selectedEntity.state.type === 'inpaint_mask') { - currentFill = { ...selectedEntity.state.fill.color, a: this.getSettings().maskOpacity }; - } + return selectedEntity.state.fill.color; + } else { + const state = this.getState(); + return state.tool.fill; } - return currentFill; }; $transformingEntity = $transformingEntity; diff --git a/invokeai/frontend/web/src/features/controlLayers/konva/patterns/getPatternSVG.ts b/invokeai/frontend/web/src/features/controlLayers/konva/patterns/getPatternSVG.ts index 2836b13979..aaf4a2c577 100644 --- a/invokeai/frontend/web/src/features/controlLayers/konva/patterns/getPatternSVG.ts +++ b/invokeai/frontend/web/src/features/controlLayers/konva/patterns/getPatternSVG.ts @@ -1,5 +1,5 @@ -import { rgbColorToString } from 'common/util/colorCodeTransformers'; -import type { FillStyle, RgbColor } from 'features/controlLayers/store/types'; +import { rgbaColorToString } from 'common/util/colorCodeTransformers'; +import type { FillStyle, RgbaColor } from 'features/controlLayers/store/types'; import crosshatch from './pattern-crosshatch.svg?raw'; import diagonal from './pattern-diagonal.svg?raw'; @@ -7,7 +7,7 @@ import grid from './pattern-grid.svg?raw'; import horizontal from './pattern-horizontal.svg?raw'; import vertical from './pattern-vertical.svg?raw'; -export function getPatternSVG(pattern: Exclude, color: RgbColor) { +export function getPatternSVG(pattern: Exclude, color: RgbaColor) { let content: string = 'data:image/svg+xml;utf8,'; if (pattern === 'crosshatch') { content += crosshatch; @@ -21,7 +21,7 @@ export function getPatternSVG(pattern: Exclude, color: RgbCo content += grid; } - content = content.replaceAll('stroke:black', `stroke:${rgbColorToString(color)}`); + content = content.replaceAll('stroke:black', `stroke:${rgbaColorToString(color)}`); return content; } diff --git a/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts b/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts index 3f9d53b6a5..55a6fdb2ca 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/canvasV2Slice.ts @@ -40,7 +40,7 @@ import type { FilterConfig, StageAttrs, } from './types'; -import { IMAGE_FILTERS, isDrawableEntity, RGBA_RED } from './types'; +import { IMAGE_FILTERS, isDrawableEntity } from './types'; const initialState: CanvasV2State = { _version: 3, @@ -55,7 +55,7 @@ const initialState: CanvasV2State = { type: 'inpaint_mask', fill: { style: 'diagonal', - color: RGBA_RED, + color: { r: 255, g: 122, b: 0, a: 1 }, // some orange color }, rasterizationCache: [], isEnabled: true, @@ -69,7 +69,7 @@ const initialState: CanvasV2State = { selected: 'view', selectedBuffer: null, invertScroll: false, - fill: RGBA_RED, + fill: { r: 31, g: 160, b: 224, a: 1 }, // invokeBlue.500 brush: { width: 50, }, @@ -87,7 +87,6 @@ const initialState: CanvasV2State = { }, }, settings: { - maskOpacity: 0.3, // TODO(psyche): These are copied from old canvas state, need to be implemented autoSave: false, imageSmoothing: true, @@ -471,7 +470,6 @@ export const { invertScrollChanged, toolChanged, toolBufferChanged, - maskOpacityChanged, allEntitiesDeleted, clipToBboxChanged, canvasReset, diff --git a/invokeai/frontend/web/src/features/controlLayers/store/inpaintMaskReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/inpaintMaskReducers.ts index b1a954ca5c..03f9d60061 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/inpaintMaskReducers.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/inpaintMaskReducers.ts @@ -1,6 +1,5 @@ import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit'; -import type { CanvasInpaintMaskState, CanvasV2State, FillStyle } from 'features/controlLayers/store/types'; -import type { RgbColor } from 'react-colorful'; +import type { CanvasInpaintMaskState, CanvasV2State, FillStyle, RgbaColor } from 'features/controlLayers/store/types'; export const inpaintMaskReducers = { imRecalled: (state, action: PayloadAction<{ data: CanvasInpaintMaskState }>) => { @@ -8,7 +7,7 @@ export const inpaintMaskReducers = { state.inpaintMask = data; state.selectedEntityIdentifier = { type: 'inpaint_mask', id: data.id }; }, - imFillColorChanged: (state, action: PayloadAction<{ color: RgbColor }>) => { + imFillColorChanged: (state, action: PayloadAction<{ color: RgbaColor }>) => { const { color } = action.payload; state.inpaintMask.fill.color = color; }, diff --git a/invokeai/frontend/web/src/features/controlLayers/store/regionsReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/regionsReducers.ts index 6aa4a171c4..aebd1cb410 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/regionsReducers.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/regionsReducers.ts @@ -6,6 +6,7 @@ import type { FillStyle, IPMethodV2, RegionalGuidanceIPAdapterConfig, + RgbaColor, } from 'features/controlLayers/store/types'; import { imageDTOToImageWithDims } from 'features/controlLayers/store/types'; import { zModelIdentifierField } from 'features/nodes/types/common'; @@ -14,7 +15,7 @@ import { isEqual } from 'lodash-es'; import type { ImageDTO, IPAdapterModelConfig } from 'services/api/types'; import { assert } from 'tsafe'; -import type { CanvasRegionalGuidanceState, RgbColor } from './types'; +import type { CanvasRegionalGuidanceState } from './types'; export const selectRegionalGuidanceEntity = (state: CanvasV2State, id: string) => { return state.regions.entities.find((rg) => rg.id === id); @@ -32,17 +33,17 @@ export const selectRegionalGuidanceEntityOrThrow = (state: CanvasV2State, id: st return rg; }; -const DEFAULT_MASK_COLORS: RgbColor[] = [ - { r: 121, g: 157, b: 219 }, // rgb(121, 157, 219) - { r: 131, g: 214, b: 131 }, // rgb(131, 214, 131) - { r: 250, g: 225, b: 80 }, // rgb(250, 225, 80) - { r: 220, g: 144, b: 101 }, // rgb(220, 144, 101) - { r: 224, g: 117, b: 117 }, // rgb(224, 117, 117) - { r: 213, g: 139, b: 202 }, // rgb(213, 139, 202) - { r: 161, g: 120, b: 214 }, // rgb(161, 120, 214) +const DEFAULT_MASK_COLORS: RgbaColor[] = [ + { r: 121, g: 157, b: 219, a: 0.5 }, // rgb(121, 157, 219) + { r: 131, g: 214, b: 131, a: 0.5 }, // rgb(131, 214, 131) + { r: 250, g: 225, b: 80, a: 0.5 }, // rgb(250, 225, 80) + { r: 220, g: 144, b: 101, a: 0.5 }, // rgb(220, 144, 101) + { r: 224, g: 117, b: 117, a: 0.5 }, // rgb(224, 117, 117) + { r: 213, g: 139, b: 202, a: 0.5 }, // rgb(213, 139, 202) + { r: 161, g: 120, b: 214, a: 0.5 }, // rgb(161, 120, 214) ]; -const getRGMaskFill = (state: CanvasV2State): RgbColor => { +const getRGMaskFill = (state: CanvasV2State): RgbaColor => { const lastFill = state.regions.entities.slice(-1)[0]?.fill.color; let i = DEFAULT_MASK_COLORS.findIndex((c) => isEqual(c, lastFill)); if (i === -1) { @@ -104,7 +105,7 @@ export const regionsReducers = { } entity.negativePrompt = prompt; }, - rgFillColorChanged: (state, action: PayloadAction<{ id: string; color: RgbColor }>) => { + rgFillColorChanged: (state, action: PayloadAction<{ id: string; color: RgbaColor }>) => { const { id, color } = action.payload; const entity = selectRegionalGuidanceEntity(state, id); if (!entity) { diff --git a/invokeai/frontend/web/src/features/controlLayers/store/settingsReducers.ts b/invokeai/frontend/web/src/features/controlLayers/store/settingsReducers.ts index d9f9a8d3e7..cecdd38135 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/settingsReducers.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/settingsReducers.ts @@ -2,9 +2,6 @@ import type { PayloadAction, SliceCaseReducers } from '@reduxjs/toolkit'; import type { CanvasV2State } from 'features/controlLayers/store/types'; export const settingsReducers = { - maskOpacityChanged: (state, action: PayloadAction) => { - state.settings.maskOpacity = action.payload; - }, clipToBboxChanged: (state, action: PayloadAction) => { state.settings.clipToBbox = action.payload; }, diff --git a/invokeai/frontend/web/src/features/controlLayers/store/types.ts b/invokeai/frontend/web/src/features/controlLayers/store/types.ts index 3598d4a412..3d37342a07 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/types.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/types.ts @@ -644,7 +644,7 @@ const zMaskObject = z const zFillStyle = z.enum(['solid', 'grid', 'crosshatch', 'diagonal', 'horizontal', 'vertical']); export type FillStyle = z.infer; export const isFillStyle = (v: unknown): v is FillStyle => zFillStyle.safeParse(v).success; -const zFill = z.object({ style: zFillStyle, color: zRgbColor }); +const zFill = z.object({ style: zFillStyle, color: zRgbaColor }); export type Fill = z.infer; const zImageCache = z.object({ @@ -858,7 +858,6 @@ export type CanvasV2State = { }; settings: { imageSmoothing: boolean; - maskOpacity: number; showHUD: boolean; autoSave: boolean; preserveMaskedArea: boolean;