diff --git a/invokeai/frontend/web/public/locales/en.json b/invokeai/frontend/web/public/locales/en.json index 756eed45b6..54428949a5 100644 --- a/invokeai/frontend/web/public/locales/en.json +++ b/invokeai/frontend/web/public/locales/en.json @@ -1716,6 +1716,12 @@ "view": "View", "transform": "Transform", "eyeDropper": "Eye Dropper" + }, + "background": { + "backgroundStyle": "Background Style", + "solid": "Solid", + "checkerboard": "Checkerboard", + "dynamicGrid": "Dynamic Grid" } }, "upscaling": { diff --git a/invokeai/frontend/web/src/features/controlLayers/components/CanvasSettingsBackgroundStyle.tsx b/invokeai/frontend/web/src/features/controlLayers/components/CanvasSettingsBackgroundStyle.tsx new file mode 100644 index 0000000000..20ea323680 --- /dev/null +++ b/invokeai/frontend/web/src/features/controlLayers/components/CanvasSettingsBackgroundStyle.tsx @@ -0,0 +1,50 @@ +import type { ComboboxOnChange, ComboboxOption } from '@invoke-ai/ui-library'; +import { Combobox, FormControl, FormLabel } from '@invoke-ai/ui-library'; +import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { canvasBackgroundStyleChanged } from 'features/controlLayers/store/canvasV2Slice'; +import { isCanvasBackgroundStyle } from 'features/controlLayers/store/types'; +import { memo, useCallback, useMemo } from 'react'; +import { useTranslation } from 'react-i18next'; + +export const CanvasSettingsBackgroundStyle = memo(() => { + const { t } = useTranslation(); + const dispatch = useAppDispatch(); + const canvasBackgroundStyle = useAppSelector((s) => s.canvasV2.settings.canvasBackgroundStyle); + const onChange = useCallback( + (v) => { + if (!isCanvasBackgroundStyle(v?.value)) { + return; + } + dispatch(canvasBackgroundStyleChanged(v.value)); + }, + [dispatch] + ); + + const options = useMemo(() => { + return [ + { + value: 'solid', + label: t('controlLayers.background.solid'), + }, + { + value: 'checkerboard', + label: t('controlLayers.background.checkerboard'), + }, + { + value: 'dynamicGrid', + label: t('controlLayers.background.dynamicGrid'), + }, + ]; + }, [t]); + + const value = useMemo(() => options.find((o) => o.value === canvasBackgroundStyle), [options, canvasBackgroundStyle]); + + return ( + + {t('controlLayers.background.backgroundStyle')} + + + ); +}); + +CanvasSettingsBackgroundStyle.displayName = 'CanvasSettingsBackgroundStyle'; diff --git a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersSettingsPopover.tsx b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersSettingsPopover.tsx index 8a7eb0b98e..a6dca5eb90 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersSettingsPopover.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/ControlLayersSettingsPopover.tsx @@ -12,6 +12,7 @@ import { } from '@invoke-ai/ui-library'; import { useStore } from '@nanostores/react'; import { useAppDispatch, useAppSelector } from 'app/store/storeHooks'; +import { CanvasSettingsBackgroundStyle } from 'features/controlLayers/components/CanvasSettingsBackgroundStyle'; import { $canvasManager } from 'features/controlLayers/konva/CanvasManager'; import { clipToBboxChanged, @@ -71,6 +72,7 @@ const ControlLayersSettingsPopover = () => { {t('unifiedCanvas.clipToBbox')} + diff --git a/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx b/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx index ae8e217df8..9870e3c72d 100644 --- a/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx +++ b/invokeai/frontend/web/src/features/controlLayers/components/StageComponent.tsx @@ -3,8 +3,10 @@ import { useStore } from '@nanostores/react'; import { $socket } from 'app/hooks/useSocketIO'; import { logger } from 'app/logging/logger'; import { useAppStore } from 'app/store/nanostores/store'; +import { useAppSelector } from 'app/store/storeHooks'; import { HeadsUpDisplay } from 'features/controlLayers/components/HeadsUpDisplay'; import { $canvasManager, CanvasManager } from 'features/controlLayers/konva/CanvasManager'; +import { TRANSPARENCY_CHECKER_PATTERN } from 'features/controlLayers/konva/constants'; import Konva from 'konva'; import { memo, useCallback, useEffect, useLayoutEffect, useState } from 'react'; import { useDevicePixelRatio } from 'use-device-pixel-ratio'; @@ -52,6 +54,8 @@ type Props = { }; export const StageComponent = memo(({ asPreview = false }: Props) => { + const canvasBackgroundStyle = useAppSelector((s) => s.canvasV2.settings.canvasBackgroundStyle); + const [stage] = useState( () => new Konva.Stage({ @@ -77,6 +81,17 @@ export const StageComponent = memo(({ asPreview = false }: Props) => { return ( + {canvasBackgroundStyle === 'checkerboard' && ( + + )} ) => { state.settings.clipToBbox = action.payload; }, + canvasBackgroundStyleChanged: (state, action: PayloadAction) => { + state.settings.canvasBackgroundStyle = action.payload; + }, } satisfies SliceCaseReducers; diff --git a/invokeai/frontend/web/src/features/controlLayers/store/types.ts b/invokeai/frontend/web/src/features/controlLayers/store/types.ts index 3d37342a07..ea010ff06b 100644 --- a/invokeai/frontend/web/src/features/controlLayers/store/types.ts +++ b/invokeai/frontend/web/src/features/controlLayers/store/types.ts @@ -839,6 +839,11 @@ export type StagingAreaImage = { offsetY: number; }; +const zCanvasBackgroundStyle = z.enum(['checkerboard', 'dynamicGrid', 'solid']); +export type CanvasBackgroundStyle = z.infer; +export const isCanvasBackgroundStyle = (v: unknown): v is CanvasBackgroundStyle => + zCanvasBackgroundStyle.safeParse(v).success; + export type CanvasV2State = { _version: 3; selectedEntityIdentifier: CanvasEntityIdentifier | null; @@ -863,6 +868,7 @@ export type CanvasV2State = { preserveMaskedArea: boolean; cropToBboxOnSave: boolean; clipToBbox: boolean; + canvasBackgroundStyle: CanvasBackgroundStyle; }; bbox: { rect: {