mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
Merge branch 'main' into sdxl_t2i_bgr
This commit is contained in:
@@ -1682,6 +1682,8 @@
|
|||||||
"controlLayer": "Control Layer",
|
"controlLayer": "Control Layer",
|
||||||
"inpaintMask": "Inpaint Mask",
|
"inpaintMask": "Inpaint Mask",
|
||||||
"regionalGuidance": "Regional Guidance",
|
"regionalGuidance": "Regional Guidance",
|
||||||
|
"canvasAsRasterLayer": "$t(controlLayers.canvas) as $t(controlLayers.rasterLayer)",
|
||||||
|
"canvasAsControlLayer": "$t(controlLayers.canvas) as $t(controlLayers.controlLayer)",
|
||||||
"referenceImage": "Reference Image",
|
"referenceImage": "Reference Image",
|
||||||
"regionalReferenceImage": "Regional Reference Image",
|
"regionalReferenceImage": "Regional Reference Image",
|
||||||
"globalReferenceImage": "Global Reference Image",
|
"globalReferenceImage": "Global Reference Image",
|
||||||
@@ -1894,6 +1896,7 @@
|
|||||||
"include": "Include",
|
"include": "Include",
|
||||||
"exclude": "Exclude",
|
"exclude": "Exclude",
|
||||||
"neutral": "Neutral",
|
"neutral": "Neutral",
|
||||||
|
"apply": "Apply",
|
||||||
"reset": "Reset",
|
"reset": "Reset",
|
||||||
"saveAs": "Save As",
|
"saveAs": "Save As",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export const CanvasAlertsPreserveMask = memo(() => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert status="warning" borderRadius="base" fontSize="sm" shadow="md" w="fit-content" alignSelf="flex-end">
|
<Alert status="warning" borderRadius="base" fontSize="sm" shadow="md" w="fit-content">
|
||||||
<AlertIcon />
|
<AlertIcon />
|
||||||
<AlertTitle>{t('controlLayers.settings.preserveMask.alert')}</AlertTitle>
|
<AlertTitle>{t('controlLayers.settings.preserveMask.alert')}</AlertTitle>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ const CanvasAlertsSelectedEntityStatusContent = memo(({ entityIdentifier, adapte
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Alert status={alert.status} borderRadius="base" fontSize="sm" shadow="md" w="fit-content" alignSelf="flex-end">
|
<Alert status={alert.status} borderRadius="base" fontSize="sm" shadow="md" w="fit-content">
|
||||||
<AlertIcon />
|
<AlertIcon />
|
||||||
<AlertTitle>{alert.title}</AlertTitle>
|
<AlertTitle>{alert.title}</AlertTitle>
|
||||||
</Alert>
|
</Alert>
|
||||||
|
|||||||
@@ -132,7 +132,6 @@ const AlertWrapper = ({
|
|||||||
fontSize="sm"
|
fontSize="sm"
|
||||||
shadow="md"
|
shadow="md"
|
||||||
w="fit-content"
|
w="fit-content"
|
||||||
alignSelf="flex-end"
|
|
||||||
>
|
>
|
||||||
<Flex w="full" alignItems="center">
|
<Flex w="full" alignItems="center">
|
||||||
<AlertIcon />
|
<AlertIcon />
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import { MenuGroup } from '@invoke-ai/ui-library';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { ControlLayerMenuItems } from 'features/controlLayers/components/ControlLayer/ControlLayerMenuItems';
|
import { ControlLayerMenuItems } from 'features/controlLayers/components/ControlLayer/ControlLayerMenuItems';
|
||||||
import { InpaintMaskMenuItems } from 'features/controlLayers/components/InpaintMask/InpaintMaskMenuItems';
|
import { InpaintMaskMenuItems } from 'features/controlLayers/components/InpaintMask/InpaintMaskMenuItems';
|
||||||
@@ -8,7 +9,9 @@ import {
|
|||||||
EntityIdentifierContext,
|
EntityIdentifierContext,
|
||||||
useEntityIdentifierContext,
|
useEntityIdentifierContext,
|
||||||
} from 'features/controlLayers/contexts/EntityIdentifierContext';
|
} from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
|
import { useEntityTypeString } from 'features/controlLayers/hooks/useEntityTypeString';
|
||||||
import { selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors';
|
import { selectSelectedEntityIdentifier } from 'features/controlLayers/store/selectors';
|
||||||
|
import type { PropsWithChildren } from 'react';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
import type { Equals } from 'tsafe';
|
import type { Equals } from 'tsafe';
|
||||||
import { assert } from 'tsafe';
|
import { assert } from 'tsafe';
|
||||||
@@ -46,9 +49,20 @@ export const CanvasContextMenuSelectedEntityMenuItems = memo(() => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<EntityIdentifierContext.Provider value={selectedEntityIdentifier}>
|
<EntityIdentifierContext.Provider value={selectedEntityIdentifier}>
|
||||||
<CanvasContextMenuSelectedEntityMenuItemsContent />
|
<CanvasContextMenuSelectedEntityMenuGroup>
|
||||||
|
<CanvasContextMenuSelectedEntityMenuItemsContent />
|
||||||
|
</CanvasContextMenuSelectedEntityMenuGroup>
|
||||||
</EntityIdentifierContext.Provider>
|
</EntityIdentifierContext.Provider>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
CanvasContextMenuSelectedEntityMenuItems.displayName = 'CanvasContextMenuSelectedEntityMenuItems';
|
CanvasContextMenuSelectedEntityMenuItems.displayName = 'CanvasContextMenuSelectedEntityMenuItems';
|
||||||
|
|
||||||
|
const CanvasContextMenuSelectedEntityMenuGroup = memo((props: PropsWithChildren) => {
|
||||||
|
const entityIdentifier = useEntityIdentifierContext();
|
||||||
|
const title = useEntityTypeString(entityIdentifier.type);
|
||||||
|
|
||||||
|
return <MenuGroup title={title}>{props.children}</MenuGroup>;
|
||||||
|
});
|
||||||
|
|
||||||
|
CanvasContextMenuSelectedEntityMenuGroup.displayName = 'CanvasContextMenuSelectedEntityMenuGroup';
|
||||||
|
|||||||
@@ -71,12 +71,16 @@ export const CanvasMainPanelContent = memo(() => {
|
|||||||
>
|
>
|
||||||
<InvokeCanvasComponent />
|
<InvokeCanvasComponent />
|
||||||
<CanvasManagerProviderGate>
|
<CanvasManagerProviderGate>
|
||||||
{showHUD && (
|
<Flex
|
||||||
<Flex position="absolute" top={1} insetInlineStart={1} pointerEvents="none">
|
position="absolute"
|
||||||
<CanvasHUD />
|
flexDir="column"
|
||||||
</Flex>
|
top={1}
|
||||||
)}
|
insetInlineStart={1}
|
||||||
<Flex flexDir="column" position="absolute" top={1} insetInlineEnd={1} pointerEvents="none" gap={2}>
|
pointerEvents="none"
|
||||||
|
gap={2}
|
||||||
|
alignItems="flex-start"
|
||||||
|
>
|
||||||
|
{showHUD && <CanvasHUD />}
|
||||||
<CanvasAlertsSelectedEntityStatus />
|
<CanvasAlertsSelectedEntityStatus />
|
||||||
<CanvasAlertsPreserveMask />
|
<CanvasAlertsPreserveMask />
|
||||||
<CanvasAlertsSendingToGallery />
|
<CanvasAlertsSendingToGallery />
|
||||||
|
|||||||
@@ -27,11 +27,10 @@ export const ControlLayerMenuItems = memo(() => {
|
|||||||
<CanvasEntityMenuItemsSelectObject />
|
<CanvasEntityMenuItemsSelectObject />
|
||||||
<ControlLayerMenuItemsTransparencyEffect />
|
<ControlLayerMenuItemsTransparencyEffect />
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
|
<ControlLayerMenuItemsCopyToSubMenu />
|
||||||
|
<ControlLayerMenuItemsConvertToSubMenu />
|
||||||
<CanvasEntityMenuItemsCropToBbox />
|
<CanvasEntityMenuItemsCropToBbox />
|
||||||
<CanvasEntityMenuItemsSave />
|
<CanvasEntityMenuItemsSave />
|
||||||
<MenuDivider />
|
|
||||||
<ControlLayerMenuItemsConvertToSubMenu />
|
|
||||||
<ControlLayerMenuItemsCopyToSubMenu />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
MenuList,
|
MenuList,
|
||||||
Spacer,
|
Spacer,
|
||||||
|
Spinner,
|
||||||
} from '@invoke-ai/ui-library';
|
} from '@invoke-ai/ui-library';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
@@ -25,7 +26,7 @@ import { IMAGE_FILTERS } from 'features/controlLayers/store/filters';
|
|||||||
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
|
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
|
||||||
import { memo, useCallback, useMemo, useRef } from 'react';
|
import { memo, useCallback, useMemo, useRef } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiArrowsCounterClockwiseBold, PiFloppyDiskBold, PiPlayFill, PiXBold } from 'react-icons/pi';
|
import { PiCaretDownBold } from 'react-icons/pi';
|
||||||
|
|
||||||
const FilterContent = memo(
|
const FilterContent = memo(
|
||||||
({ adapter }: { adapter: CanvasEntityAdapterRasterLayer | CanvasEntityAdapterControlLayer }) => {
|
({ adapter }: { adapter: CanvasEntityAdapterRasterLayer | CanvasEntityAdapterControlLayer }) => {
|
||||||
@@ -115,39 +116,41 @@ const FilterContent = memo(
|
|||||||
<ButtonGroup isAttached={false} size="sm" w="full">
|
<ButtonGroup isAttached={false} size="sm" w="full">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
leftIcon={<PiPlayFill />}
|
|
||||||
onClick={adapter.filterer.processImmediate}
|
onClick={adapter.filterer.processImmediate}
|
||||||
isLoading={isProcessing}
|
|
||||||
loadingText={t('controlLayers.filter.process')}
|
loadingText={t('controlLayers.filter.process')}
|
||||||
isDisabled={!isValid || autoProcess}
|
isDisabled={isProcessing || !isValid || autoProcess}
|
||||||
>
|
>
|
||||||
{t('controlLayers.filter.process')}
|
{t('controlLayers.filter.process')}
|
||||||
|
{isProcessing && <Spinner ms={3} boxSize={5} color="base.600" />}
|
||||||
</Button>
|
</Button>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<PiArrowsCounterClockwiseBold />}
|
|
||||||
onClick={adapter.filterer.reset}
|
onClick={adapter.filterer.reset}
|
||||||
isLoading={isProcessing}
|
isDisabled={isProcessing}
|
||||||
loadingText={t('controlLayers.filter.reset')}
|
loadingText={t('controlLayers.filter.reset')}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
{t('controlLayers.filter.reset')}
|
{t('controlLayers.filter.reset')}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={adapter.filterer.apply}
|
||||||
|
loadingText={t('controlLayers.filter.apply')}
|
||||||
|
variant="ghost"
|
||||||
|
isDisabled={isProcessing || !isValid || !hasProcessed}
|
||||||
|
>
|
||||||
|
{t('controlLayers.filter.apply')}
|
||||||
|
</Button>
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuButton
|
<MenuButton
|
||||||
as={Button}
|
as={Button}
|
||||||
leftIcon={<PiFloppyDiskBold />}
|
|
||||||
isLoading={isProcessing}
|
|
||||||
loadingText={t('controlLayers.selectObject.saveAs')}
|
loadingText={t('controlLayers.selectObject.saveAs')}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
isDisabled={!isValid || !hasProcessed}
|
isDisabled={isProcessing || !isValid || !hasProcessed}
|
||||||
|
rightIcon={<PiCaretDownBold />}
|
||||||
>
|
>
|
||||||
{t('controlLayers.selectObject.saveAs')}
|
{t('controlLayers.selectObject.saveAs')}
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
<MenuList>
|
<MenuList>
|
||||||
<MenuItem isDisabled={!isValid || !hasProcessed} onClick={adapter.filterer.apply}>
|
|
||||||
{t('controlLayers.replaceCurrent')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem isDisabled={!isValid || !hasProcessed} onClick={saveAsInpaintMask}>
|
<MenuItem isDisabled={!isValid || !hasProcessed} onClick={saveAsInpaintMask}>
|
||||||
{t('controlLayers.newInpaintMask')}
|
{t('controlLayers.newInpaintMask')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
@@ -162,12 +165,7 @@ const FilterContent = memo(
|
|||||||
</MenuItem>
|
</MenuItem>
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</Menu>
|
</Menu>
|
||||||
<Button
|
<Button variant="ghost" onClick={adapter.filterer.cancel} loadingText={t('controlLayers.filter.cancel')}>
|
||||||
variant="ghost"
|
|
||||||
leftIcon={<PiXBold />}
|
|
||||||
onClick={adapter.filterer.cancel}
|
|
||||||
loadingText={t('controlLayers.filter.cancel')}
|
|
||||||
>
|
|
||||||
{t('controlLayers.filter.cancel')}
|
{t('controlLayers.filter.cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
</ButtonGroup>
|
</ButtonGroup>
|
||||||
|
|||||||
@@ -20,10 +20,9 @@ export const InpaintMaskMenuItems = memo(() => {
|
|||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<CanvasEntityMenuItemsTransform />
|
<CanvasEntityMenuItemsTransform />
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<CanvasEntityMenuItemsCropToBbox />
|
|
||||||
<MenuDivider />
|
|
||||||
<InpaintMaskMenuItemsConvertToSubMenu />
|
|
||||||
<InpaintMaskMenuItemsCopyToSubMenu />
|
<InpaintMaskMenuItemsCopyToSubMenu />
|
||||||
|
<InpaintMaskMenuItemsConvertToSubMenu />
|
||||||
|
<CanvasEntityMenuItemsCropToBbox />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -25,11 +25,10 @@ export const RasterLayerMenuItems = memo(() => {
|
|||||||
<CanvasEntityMenuItemsFilter />
|
<CanvasEntityMenuItemsFilter />
|
||||||
<CanvasEntityMenuItemsSelectObject />
|
<CanvasEntityMenuItemsSelectObject />
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
|
<RasterLayerMenuItemsCopyToSubMenu />
|
||||||
|
<RasterLayerMenuItemsConvertToSubMenu />
|
||||||
<CanvasEntityMenuItemsCropToBbox />
|
<CanvasEntityMenuItemsCropToBbox />
|
||||||
<CanvasEntityMenuItemsSave />
|
<CanvasEntityMenuItemsSave />
|
||||||
<MenuDivider />
|
|
||||||
<RasterLayerMenuItemsConvertToSubMenu />
|
|
||||||
<RasterLayerMenuItemsCopyToSubMenu />
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -25,10 +25,9 @@ export const RegionalGuidanceMenuItems = memo(() => {
|
|||||||
<CanvasEntityMenuItemsTransform />
|
<CanvasEntityMenuItemsTransform />
|
||||||
<RegionalGuidanceMenuItemsAutoNegative />
|
<RegionalGuidanceMenuItemsAutoNegative />
|
||||||
<MenuDivider />
|
<MenuDivider />
|
||||||
<CanvasEntityMenuItemsCropToBbox />
|
|
||||||
<MenuDivider />
|
|
||||||
<RegionalGuidanceMenuItemsConvertToSubMenu />
|
|
||||||
<RegionalGuidanceMenuItemsCopyToSubMenu />
|
<RegionalGuidanceMenuItemsCopyToSubMenu />
|
||||||
|
<RegionalGuidanceMenuItemsConvertToSubMenu />
|
||||||
|
<CanvasEntityMenuItemsCropToBbox />
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import {
|
|||||||
MenuItem,
|
MenuItem,
|
||||||
MenuList,
|
MenuList,
|
||||||
Spacer,
|
Spacer,
|
||||||
|
Spinner,
|
||||||
Text,
|
Text,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
UnorderedList,
|
UnorderedList,
|
||||||
@@ -29,7 +30,7 @@ import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/us
|
|||||||
import type { PropsWithChildren } from 'react';
|
import type { PropsWithChildren } from 'react';
|
||||||
import { memo, useCallback, useRef } from 'react';
|
import { memo, useCallback, useRef } from 'react';
|
||||||
import { Trans, useTranslation } from 'react-i18next';
|
import { Trans, useTranslation } from 'react-i18next';
|
||||||
import { PiArrowsCounterClockwiseBold, PiFloppyDiskBold, PiInfoBold, PiPlayFill, PiXBold } from 'react-icons/pi';
|
import { PiCaretDownBold, PiInfoBold } from 'react-icons/pi';
|
||||||
|
|
||||||
const SelectObjectContent = memo(
|
const SelectObjectContent = memo(
|
||||||
({ adapter }: { adapter: CanvasEntityAdapterRasterLayer | CanvasEntityAdapterControlLayer }) => {
|
({ adapter }: { adapter: CanvasEntityAdapterRasterLayer | CanvasEntityAdapterControlLayer }) => {
|
||||||
@@ -42,10 +43,6 @@ const SelectObjectContent = memo(
|
|||||||
const hasImageState = useStore(adapter.segmentAnything.$hasImageState);
|
const hasImageState = useStore(adapter.segmentAnything.$hasImageState);
|
||||||
const autoProcess = useAppSelector(selectAutoProcess);
|
const autoProcess = useAppSelector(selectAutoProcess);
|
||||||
|
|
||||||
const replaceCurrent = useCallback(() => {
|
|
||||||
adapter.segmentAnything.apply();
|
|
||||||
}, [adapter.segmentAnything]);
|
|
||||||
|
|
||||||
const saveAsInpaintMask = useCallback(() => {
|
const saveAsInpaintMask = useCallback(() => {
|
||||||
adapter.segmentAnything.saveAs('inpaint_mask');
|
adapter.segmentAnything.saveAs('inpaint_mask');
|
||||||
}, [adapter.segmentAnything]);
|
}, [adapter.segmentAnything]);
|
||||||
@@ -115,58 +112,59 @@ const SelectObjectContent = memo(
|
|||||||
|
|
||||||
<ButtonGroup isAttached={false} size="sm" w="full">
|
<ButtonGroup isAttached={false} size="sm" w="full">
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<PiPlayFill />}
|
|
||||||
onClick={adapter.segmentAnything.processImmediate}
|
onClick={adapter.segmentAnything.processImmediate}
|
||||||
isLoading={isProcessing}
|
|
||||||
loadingText={t('controlLayers.selectObject.process')}
|
loadingText={t('controlLayers.selectObject.process')}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
isDisabled={!hasPoints || autoProcess}
|
isDisabled={isProcessing || !hasPoints || autoProcess}
|
||||||
>
|
>
|
||||||
{t('controlLayers.selectObject.process')}
|
{t('controlLayers.selectObject.process')}
|
||||||
|
{isProcessing && <Spinner ms={3} boxSize={5} color="base.600" />}
|
||||||
</Button>
|
</Button>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<PiArrowsCounterClockwiseBold />}
|
|
||||||
onClick={adapter.segmentAnything.reset}
|
onClick={adapter.segmentAnything.reset}
|
||||||
isLoading={isProcessing}
|
isDisabled={isProcessing || !hasPoints}
|
||||||
loadingText={t('controlLayers.selectObject.reset')}
|
loadingText={t('controlLayers.selectObject.reset')}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
{t('controlLayers.selectObject.reset')}
|
{t('controlLayers.selectObject.reset')}
|
||||||
</Button>
|
</Button>
|
||||||
|
<Button
|
||||||
|
onClick={adapter.segmentAnything.apply}
|
||||||
|
loadingText={t('controlLayers.selectObject.apply')}
|
||||||
|
variant="ghost"
|
||||||
|
isDisabled={isProcessing || !hasImageState}
|
||||||
|
>
|
||||||
|
{t('controlLayers.selectObject.apply')}
|
||||||
|
</Button>
|
||||||
<Menu>
|
<Menu>
|
||||||
<MenuButton
|
<MenuButton
|
||||||
as={Button}
|
as={Button}
|
||||||
leftIcon={<PiFloppyDiskBold />}
|
|
||||||
isLoading={isProcessing}
|
|
||||||
loadingText={t('controlLayers.selectObject.saveAs')}
|
loadingText={t('controlLayers.selectObject.saveAs')}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
isDisabled={!hasImageState}
|
isDisabled={isProcessing || !hasImageState}
|
||||||
|
rightIcon={<PiCaretDownBold />}
|
||||||
>
|
>
|
||||||
{t('controlLayers.selectObject.saveAs')}
|
{t('controlLayers.selectObject.saveAs')}
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
<MenuList>
|
<MenuList>
|
||||||
<MenuItem isDisabled={!hasImageState} onClick={replaceCurrent}>
|
<MenuItem isDisabled={isProcessing || !hasImageState} onClick={saveAsInpaintMask}>
|
||||||
{t('controlLayers.replaceCurrent')}
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem isDisabled={!hasImageState} onClick={saveAsInpaintMask}>
|
|
||||||
{t('controlLayers.newInpaintMask')}
|
{t('controlLayers.newInpaintMask')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem isDisabled={!hasImageState} onClick={saveAsRegionalGuidance}>
|
<MenuItem isDisabled={isProcessing || !hasImageState} onClick={saveAsRegionalGuidance}>
|
||||||
{t('controlLayers.newRegionalGuidance')}
|
{t('controlLayers.newRegionalGuidance')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem isDisabled={!hasImageState} onClick={saveAsControlLayer}>
|
<MenuItem isDisabled={isProcessing || !hasImageState} onClick={saveAsControlLayer}>
|
||||||
{t('controlLayers.newControlLayer')}
|
{t('controlLayers.newControlLayer')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem isDisabled={!hasImageState} onClick={saveAsRasterLayer}>
|
<MenuItem isDisabled={isProcessing || !hasImageState} onClick={saveAsRasterLayer}>
|
||||||
{t('controlLayers.newRasterLayer')}
|
{t('controlLayers.newRasterLayer')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
</MenuList>
|
</MenuList>
|
||||||
</Menu>
|
</Menu>
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<PiXBold />}
|
|
||||||
onClick={adapter.segmentAnything.cancel}
|
onClick={adapter.segmentAnything.cancel}
|
||||||
isLoading={isProcessing}
|
isDisabled={isProcessing}
|
||||||
loadingText={t('common.cancel')}
|
loadingText={t('common.cancel')}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Button, ButtonGroup, Flex, Heading, Spacer } from '@invoke-ai/ui-library';
|
import { Button, ButtonGroup, Flex, Heading, Spacer, Spinner } from '@invoke-ai/ui-library';
|
||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { useFocusRegion, useIsRegionFocused } from 'common/hooks/focus';
|
import { useFocusRegion, useIsRegionFocused } from 'common/hooks/focus';
|
||||||
import { CanvasOperationIsolatedLayerPreviewSwitch } from 'features/controlLayers/components/CanvasOperationIsolatedLayerPreviewSwitch';
|
import { CanvasOperationIsolatedLayerPreviewSwitch } from 'features/controlLayers/components/CanvasOperationIsolatedLayerPreviewSwitch';
|
||||||
@@ -8,7 +8,6 @@ import type { CanvasEntityAdapter } from 'features/controlLayers/konva/CanvasEnt
|
|||||||
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
|
import { useRegisteredHotkeys } from 'features/system/components/HotkeysModal/useHotkeyData';
|
||||||
import { memo, useRef } from 'react';
|
import { memo, useRef } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiArrowsCounterClockwiseBold, PiCheckBold, PiXBold } from 'react-icons/pi';
|
|
||||||
|
|
||||||
const TransformContent = memo(({ adapter }: { adapter: CanvasEntityAdapter }) => {
|
const TransformContent = memo(({ adapter }: { adapter: CanvasEntityAdapter }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -62,30 +61,28 @@ const TransformContent = memo(({ adapter }: { adapter: CanvasEntityAdapter }) =>
|
|||||||
|
|
||||||
<TransformFitToBboxButtons adapter={adapter} />
|
<TransformFitToBboxButtons adapter={adapter} />
|
||||||
|
|
||||||
<ButtonGroup isAttached={false} size="sm" w="full">
|
<ButtonGroup isAttached={false} size="sm" w="full" alignItems="center">
|
||||||
|
{isProcessing && <Spinner ms={3} boxSize={5} color="base.600" />}
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<PiArrowsCounterClockwiseBold />}
|
|
||||||
onClick={adapter.transformer.resetTransform}
|
onClick={adapter.transformer.resetTransform}
|
||||||
isLoading={isProcessing}
|
isDisabled={isProcessing}
|
||||||
loadingText={t('controlLayers.transform.reset')}
|
loadingText={t('controlLayers.transform.reset')}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
{t('controlLayers.transform.reset')}
|
{t('controlLayers.transform.reset')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<PiCheckBold />}
|
|
||||||
onClick={adapter.transformer.applyTransform}
|
onClick={adapter.transformer.applyTransform}
|
||||||
isLoading={isProcessing}
|
isDisabled={isProcessing}
|
||||||
loadingText={t('controlLayers.transform.apply')}
|
loadingText={t('controlLayers.transform.apply')}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
{t('controlLayers.transform.apply')}
|
{t('controlLayers.transform.apply')}
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<PiXBold />}
|
|
||||||
onClick={adapter.transformer.stopTransform}
|
onClick={adapter.transformer.stopTransform}
|
||||||
isLoading={isProcessing}
|
isDisabled={isProcessing}
|
||||||
loadingText={t('common.cancel')}
|
loadingText={t('common.cancel')}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import { useStore } from '@nanostores/react';
|
|||||||
import type { CanvasEntityAdapter } from 'features/controlLayers/konva/CanvasEntity/types';
|
import type { CanvasEntityAdapter } from 'features/controlLayers/konva/CanvasEntity/types';
|
||||||
import { memo, useCallback, useMemo, useState } from 'react';
|
import { memo, useCallback, useMemo, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiArrowsOutBold } from 'react-icons/pi';
|
|
||||||
import type { Equals } from 'tsafe';
|
import type { Equals } from 'tsafe';
|
||||||
import { assert } from 'tsafe';
|
import { assert } from 'tsafe';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
@@ -60,10 +59,9 @@ export const TransformFitToBboxButtons = memo(({ adapter }: { adapter: CanvasEnt
|
|||||||
<Combobox options={options} value={value} onChange={onChange} isSearchable={false} isClearable={false} />
|
<Combobox options={options} value={value} onChange={onChange} isSearchable={false} isClearable={false} />
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<PiArrowsOutBold />}
|
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
isLoading={isProcessing}
|
isDisabled={isProcessing}
|
||||||
loadingText={t('controlLayers.transform.fitToBbox')}
|
loadingText={t('controlLayers.transform.fitToBbox')}
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -46,6 +46,8 @@ import { useCallback } from 'react';
|
|||||||
import { modelConfigsAdapterSelectors, selectModelConfigsQuery } from 'services/api/endpoints/models';
|
import { modelConfigsAdapterSelectors, selectModelConfigsQuery } from 'services/api/endpoints/models';
|
||||||
import type { ControlNetModelConfig, ImageDTO, IPAdapterModelConfig, T2IAdapterModelConfig } from 'services/api/types';
|
import type { ControlNetModelConfig, ImageDTO, IPAdapterModelConfig, T2IAdapterModelConfig } from 'services/api/types';
|
||||||
import { isControlNetOrT2IAdapterModelConfig, isIPAdapterModelConfig } from 'services/api/types';
|
import { isControlNetOrT2IAdapterModelConfig, isIPAdapterModelConfig } from 'services/api/types';
|
||||||
|
import type { Equals } from 'tsafe';
|
||||||
|
import { assert } from 'tsafe';
|
||||||
|
|
||||||
export const selectDefaultControlAdapter = createSelector(
|
export const selectDefaultControlAdapter = createSelector(
|
||||||
selectModelConfigsQuery,
|
selectModelConfigsQuery,
|
||||||
@@ -194,18 +196,31 @@ export const useNewCanvasFromImage = () => {
|
|||||||
const bboxRect = useAppSelector(selectBboxRect);
|
const bboxRect = useAppSelector(selectBboxRect);
|
||||||
const base = useAppSelector(selectBboxModelBase);
|
const base = useAppSelector(selectBboxModelBase);
|
||||||
const func = useCallback(
|
const func = useCallback(
|
||||||
(imageDTO: ImageDTO) => {
|
(imageDTO: ImageDTO, type: CanvasRasterLayerState['type'] | CanvasControlLayerState['type']) => {
|
||||||
// Calculate the new bbox dimensions to fit the image's aspect ratio at the optimal size
|
// Calculate the new bbox dimensions to fit the image's aspect ratio at the optimal size
|
||||||
const ratio = imageDTO.width / imageDTO.height;
|
const ratio = imageDTO.width / imageDTO.height;
|
||||||
const optimalDimension = getOptimalDimension(base);
|
const optimalDimension = getOptimalDimension(base);
|
||||||
const { width, height } = calculateNewSize(ratio, optimalDimension ** 2, base);
|
const { width, height } = calculateNewSize(ratio, optimalDimension ** 2, base);
|
||||||
|
|
||||||
// The overrides need to include the layer's ID so we can transform the layer it is initialized
|
// The overrides need to include the layer's ID so we can transform the layer it is initialized
|
||||||
const overrides = {
|
let overrides: Partial<CanvasRasterLayerState> | Partial<CanvasControlLayerState>;
|
||||||
id: getPrefixedId('raster_layer'),
|
|
||||||
position: { x: bboxRect.x, y: bboxRect.y },
|
if (type === 'raster_layer') {
|
||||||
objects: [imageDTOToImageObject(imageDTO)],
|
overrides = {
|
||||||
} satisfies Partial<CanvasRasterLayerState>;
|
id: getPrefixedId('raster_layer'),
|
||||||
|
position: { x: bboxRect.x, y: bboxRect.y },
|
||||||
|
objects: [imageDTOToImageObject(imageDTO)],
|
||||||
|
} satisfies Partial<CanvasRasterLayerState>;
|
||||||
|
} else if (type === 'control_layer') {
|
||||||
|
overrides = {
|
||||||
|
id: getPrefixedId('control_layer'),
|
||||||
|
position: { x: bboxRect.x, y: bboxRect.y },
|
||||||
|
objects: [imageDTOToImageObject(imageDTO)],
|
||||||
|
} satisfies Partial<CanvasControlLayerState>;
|
||||||
|
} else {
|
||||||
|
// Catch unhandled types
|
||||||
|
assert<Equals<typeof type, never>>(false);
|
||||||
|
}
|
||||||
|
|
||||||
CanvasEntityAdapterBase.registerInitCallback(async (adapter) => {
|
CanvasEntityAdapterBase.registerInitCallback(async (adapter) => {
|
||||||
// Skip the callback if the adapter is not the one we are creating
|
// Skip the callback if the adapter is not the one we are creating
|
||||||
@@ -222,7 +237,16 @@ export const useNewCanvasFromImage = () => {
|
|||||||
dispatch(canvasReset());
|
dispatch(canvasReset());
|
||||||
// The `bboxChangedFromCanvas` reducer does no validation! Careful!
|
// The `bboxChangedFromCanvas` reducer does no validation! Careful!
|
||||||
dispatch(bboxChangedFromCanvas({ x: 0, y: 0, width, height }));
|
dispatch(bboxChangedFromCanvas({ x: 0, y: 0, width, height }));
|
||||||
dispatch(rasterLayerAdded({ overrides, isSelected: true }));
|
|
||||||
|
// The type casts are safe because the type is checked above
|
||||||
|
if (type === 'raster_layer') {
|
||||||
|
dispatch(rasterLayerAdded({ overrides: overrides as Partial<CanvasRasterLayerState>, isSelected: true }));
|
||||||
|
} else if (type === 'control_layer') {
|
||||||
|
dispatch(controlLayerAdded({ overrides: overrides as Partial<CanvasControlLayerState>, isSelected: true }));
|
||||||
|
} else {
|
||||||
|
// Catch unhandled types
|
||||||
|
assert<Equals<typeof type, never>>(false);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
[base, bboxRect.x, bboxRect.y, dispatch]
|
[base, bboxRect.x, bboxRect.y, dispatch]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -52,8 +52,9 @@ export const useEntityTransform = (entityIdentifier: CanvasEntityIdentifier | nu
|
|||||||
if (!adapter) {
|
if (!adapter) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
imageViewer.close();
|
||||||
await adapter.transformer.startTransform();
|
await adapter.transformer.startTransform();
|
||||||
}, [isDisabled, entityIdentifier, canvasManager]);
|
}, [isDisabled, entityIdentifier, canvasManager, imageViewer]);
|
||||||
|
|
||||||
const fitToBbox = useCallback(async () => {
|
const fitToBbox = useCallback(async () => {
|
||||||
if (isDisabled) {
|
if (isDisabled) {
|
||||||
|
|||||||
@@ -51,10 +51,16 @@ export class CanvasStagingAreaModule extends CanvasModuleBase {
|
|||||||
/**
|
/**
|
||||||
* Sync the $isStaging flag with the redux state. $isStaging is used by the manager to determine the global busy
|
* Sync the $isStaging flag with the redux state. $isStaging is used by the manager to determine the global busy
|
||||||
* state of the canvas.
|
* state of the canvas.
|
||||||
|
*
|
||||||
|
* We also set the $shouldShowStagedImage flag when we enter staging mode, so that the staged images are shown,
|
||||||
|
* even if the user disabled this in the last staging session.
|
||||||
*/
|
*/
|
||||||
this.subscriptions.add(
|
this.subscriptions.add(
|
||||||
this.manager.stateApi.createStoreSubscription(selectIsStaging, (isStaging) => {
|
this.manager.stateApi.createStoreSubscription(selectIsStaging, (isStaging, oldIsStaging) => {
|
||||||
this.$isStaging.set(isStaging);
|
this.$isStaging.set(isStaging);
|
||||||
|
if (isStaging && !oldIsStaging) {
|
||||||
|
this.$shouldShowStagedImage.set(true);
|
||||||
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,8 +32,19 @@ export const ImageMenuItemNewFromImageSubMenu = memo(() => {
|
|||||||
const newRegionalGuidanceFromImage = useNewRegionalGuidanceFromImage();
|
const newRegionalGuidanceFromImage = useNewRegionalGuidanceFromImage();
|
||||||
const newCanvasFromImage = useNewCanvasFromImage();
|
const newCanvasFromImage = useNewCanvasFromImage();
|
||||||
|
|
||||||
const onClickNewCanvasFromImage = useCallback(() => {
|
const onClickNewCanvasWithRasterLayerFromImage = useCallback(() => {
|
||||||
newCanvasFromImage(imageDTO);
|
newCanvasFromImage(imageDTO, 'raster_layer');
|
||||||
|
dispatch(setActiveTab('canvas'));
|
||||||
|
imageViewer.close();
|
||||||
|
toast({
|
||||||
|
id: 'SENT_TO_CANVAS',
|
||||||
|
title: t('toast.sentToCanvas'),
|
||||||
|
status: 'success',
|
||||||
|
});
|
||||||
|
}, [dispatch, imageDTO, imageViewer, newCanvasFromImage, t]);
|
||||||
|
|
||||||
|
const onClickNewCanvasWithControlLayerFromImage = useCallback(() => {
|
||||||
|
newCanvasFromImage(imageDTO, 'control_layer');
|
||||||
dispatch(setActiveTab('canvas'));
|
dispatch(setActiveTab('canvas'));
|
||||||
imageViewer.close();
|
imageViewer.close();
|
||||||
toast({
|
toast({
|
||||||
@@ -98,8 +109,15 @@ export const ImageMenuItemNewFromImageSubMenu = memo(() => {
|
|||||||
<SubMenuButtonContent label="New from Image" />
|
<SubMenuButtonContent label="New from Image" />
|
||||||
</MenuButton>
|
</MenuButton>
|
||||||
<MenuList {...subMenu.menuListProps}>
|
<MenuList {...subMenu.menuListProps}>
|
||||||
<MenuItem icon={<PiFileBold />} onClickCapture={onClickNewCanvasFromImage} isDisabled={isBusy}>
|
<MenuItem icon={<PiFileBold />} onClickCapture={onClickNewCanvasWithRasterLayerFromImage} isDisabled={isBusy}>
|
||||||
{t('controlLayers.canvas')}
|
{t('controlLayers.canvasAsRasterLayer')}
|
||||||
|
</MenuItem>
|
||||||
|
<MenuItem
|
||||||
|
icon={<PiFileBold />}
|
||||||
|
onClickCapture={onClickNewCanvasWithControlLayerFromImage}
|
||||||
|
isDisabled={isBusy}
|
||||||
|
>
|
||||||
|
{t('controlLayers.canvasAsControlLayer')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
<MenuItem icon={<NewLayerIcon />} onClickCapture={onClickNewInpaintMaskFromImage} isDisabled={isBusy}>
|
<MenuItem icon={<NewLayerIcon />} onClickCapture={onClickNewInpaintMaskFromImage} isDisabled={isBusy}>
|
||||||
{t('controlLayers.inpaintMask')}
|
{t('controlLayers.inpaintMask')}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export const ImageViewer = memo(({ closeButton }: Props) => {
|
|||||||
{hasImageToCompare && <ImageComparison containerDims={containerDims} />}
|
{hasImageToCompare && <ImageComparison containerDims={containerDims} />}
|
||||||
</Box>
|
</Box>
|
||||||
<ImageComparisonDroppable />
|
<ImageComparisonDroppable />
|
||||||
<Box position="absolute" top={14} insetInlineEnd={2}>
|
<Box position="absolute" top={14} insetInlineStart={2}>
|
||||||
<CanvasAlertsSendingToCanvas />
|
<CanvasAlertsSendingToCanvas />
|
||||||
</Box>
|
</Box>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
__version__ = "5.3.0rc2"
|
__version__ = "5.3.0"
|
||||||
|
|||||||
Reference in New Issue
Block a user