feat(ui): canvas layer preview, revised reactivity for adapters

This commit is contained in:
psychedelicious 2024-08-23 10:36:21 +10:00
parent 7b54762b5e
commit f76f1d89d7
22 changed files with 271 additions and 168 deletions

@ -3,11 +3,12 @@ import { CanvasEntityContainer } from 'features/controlLayers/components/common/
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton'; import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle'; import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader'; import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
import { CanvasEntityPreviewImage } from 'features/controlLayers/components/common/CanvasEntityPreviewImage';
import { CanvasEntitySettingsWrapper } from 'features/controlLayers/components/common/CanvasEntitySettingsWrapper'; import { CanvasEntitySettingsWrapper } from 'features/controlLayers/components/common/CanvasEntitySettingsWrapper';
import { CanvasEntityEditableTitle } from 'features/controlLayers/components/common/CanvasEntityTitleEdit'; import { CanvasEntityEditableTitle } from 'features/controlLayers/components/common/CanvasEntityTitleEdit';
import { ControlLayerControlAdapter } from 'features/controlLayers/components/ControlLayer/ControlLayerControlAdapter'; import { ControlLayerControlAdapter } from 'features/controlLayers/components/ControlLayer/ControlLayerControlAdapter';
import { EntityLayerAdapterGate } from 'features/controlLayers/contexts/EntityAdapterContext';
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { EntityLayerAdapterProviderGate } from 'features/controlLayers/hooks/useEntityLayerAdapter';
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types'; import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
@ -20,9 +21,10 @@ export const ControlLayer = memo(({ id }: Props) => {
return ( return (
<EntityIdentifierContext.Provider value={entityIdentifier}> <EntityIdentifierContext.Provider value={entityIdentifier}>
<EntityLayerAdapterProviderGate> <EntityLayerAdapterGate>
<CanvasEntityContainer> <CanvasEntityContainer>
<CanvasEntityHeader> <CanvasEntityHeader>
<CanvasEntityPreviewImage />
<CanvasEntityEnabledToggle /> <CanvasEntityEnabledToggle />
<CanvasEntityEditableTitle /> <CanvasEntityEditableTitle />
<Spacer /> <Spacer />
@ -32,7 +34,7 @@ export const ControlLayer = memo(({ id }: Props) => {
<ControlLayerControlAdapter /> <ControlLayerControlAdapter />
</CanvasEntitySettingsWrapper> </CanvasEntitySettingsWrapper>
</CanvasEntityContainer> </CanvasEntityContainer>
</EntityLayerAdapterProviderGate> </EntityLayerAdapterGate>
</EntityIdentifierContext.Provider> </EntityIdentifierContext.Provider>
); );
}); });

@ -10,16 +10,17 @@ import { ToolChooser } from 'features/controlLayers/components/Tool/ToolChooser'
import { ToolEraserWidth } from 'features/controlLayers/components/Tool/ToolEraserWidth'; import { ToolEraserWidth } from 'features/controlLayers/components/Tool/ToolEraserWidth';
import { ToolFillColorPicker } from 'features/controlLayers/components/Tool/ToolFillColorPicker'; import { ToolFillColorPicker } from 'features/controlLayers/components/Tool/ToolFillColorPicker';
import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup'; import { UndoRedoButtonGroup } from 'features/controlLayers/components/UndoRedoButtonGroup';
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate'; import { CanvasManagerProviderGate, useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import { ToggleProgressButton } from 'features/gallery/components/ImageViewer/ToggleProgressButton'; import { ToggleProgressButton } from 'features/gallery/components/ImageViewer/ToggleProgressButton';
import { ViewerToggleMenu } from 'features/gallery/components/ImageViewer/ViewerToggleMenu'; import { ViewerToggleMenu } from 'features/gallery/components/ImageViewer/ViewerToggleMenu';
import { memo } from 'react'; import { memo, useSyncExternalStore } from 'react';
export const ControlLayersToolbar = memo(() => { export const ControlLayersToolbar = memo(() => {
const tool = useAppSelector((s) => s.canvasV2.tool.selected); const tool = useAppSelector((s) => s.canvasV2.tool.selected);
return ( return (
<CanvasManagerProviderGate> <CanvasManagerProviderGate>
<Flex w="full" gap={2} alignItems="center"> <Flex w="full" gap={2} alignItems="center">
<ReactiveTest />
<ToggleProgressButton /> <ToggleProgressButton />
<ToolChooser /> <ToolChooser />
<Spacer /> <Spacer />
@ -40,3 +41,15 @@ export const ControlLayersToolbar = memo(() => {
}); });
ControlLayersToolbar.displayName = 'ControlLayersToolbar'; ControlLayersToolbar.displayName = 'ControlLayersToolbar';
const ReactiveTest = () => {
const canvasManager = useCanvasManager();
const adapters = useSyncExternalStore(
canvasManager.adapters.rasterLayers.subscribe,
canvasManager.adapters.rasterLayers.getSnapshot
);
console.log(adapters);
return null;
};

@ -3,9 +3,10 @@ import { CanvasEntityContainer } from 'features/controlLayers/components/common/
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton'; import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle'; import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader'; import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
import { CanvasEntityPreviewImage } from 'features/controlLayers/components/common/CanvasEntityPreviewImage';
import { CanvasEntityEditableTitle } from 'features/controlLayers/components/common/CanvasEntityTitleEdit'; import { CanvasEntityEditableTitle } from 'features/controlLayers/components/common/CanvasEntityTitleEdit';
import { EntityMaskAdapterGate } from 'features/controlLayers/contexts/EntityAdapterContext';
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { EntityMaskAdapterProviderGate } from 'features/controlLayers/hooks/useEntityMaskAdapter';
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types'; import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
@ -20,9 +21,10 @@ export const InpaintMask = memo(({ id }: Props) => {
return ( return (
<EntityIdentifierContext.Provider value={entityIdentifier}> <EntityIdentifierContext.Provider value={entityIdentifier}>
<EntityMaskAdapterProviderGate> <EntityMaskAdapterGate>
<CanvasEntityContainer> <CanvasEntityContainer>
<CanvasEntityHeader> <CanvasEntityHeader>
<CanvasEntityPreviewImage />
<CanvasEntityEnabledToggle /> <CanvasEntityEnabledToggle />
<CanvasEntityEditableTitle /> <CanvasEntityEditableTitle />
<Spacer /> <Spacer />
@ -30,7 +32,7 @@ export const InpaintMask = memo(({ id }: Props) => {
<CanvasEntityDeleteButton /> <CanvasEntityDeleteButton />
</CanvasEntityHeader> </CanvasEntityHeader>
</CanvasEntityContainer> </CanvasEntityContainer>
</EntityMaskAdapterProviderGate> </EntityMaskAdapterGate>
</EntityIdentifierContext.Provider> </EntityIdentifierContext.Provider>
); );
}); });

@ -3,9 +3,10 @@ import { CanvasEntityContainer } from 'features/controlLayers/components/common/
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton'; import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle'; import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader'; import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
import { CanvasEntityPreviewImage } from 'features/controlLayers/components/common/CanvasEntityPreviewImage';
import { CanvasEntityEditableTitle } from 'features/controlLayers/components/common/CanvasEntityTitleEdit'; import { CanvasEntityEditableTitle } from 'features/controlLayers/components/common/CanvasEntityTitleEdit';
import { EntityLayerAdapterGate } from 'features/controlLayers/contexts/EntityAdapterContext';
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { EntityLayerAdapterProviderGate } from 'features/controlLayers/hooks/useEntityLayerAdapter';
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types'; import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
@ -18,16 +19,17 @@ export const RasterLayer = memo(({ id }: Props) => {
return ( return (
<EntityIdentifierContext.Provider value={entityIdentifier}> <EntityIdentifierContext.Provider value={entityIdentifier}>
<EntityLayerAdapterProviderGate> <EntityLayerAdapterGate>
<CanvasEntityContainer> <CanvasEntityContainer>
<CanvasEntityHeader> <CanvasEntityHeader>
<CanvasEntityPreviewImage />
<CanvasEntityEnabledToggle /> <CanvasEntityEnabledToggle />
<CanvasEntityEditableTitle /> <CanvasEntityEditableTitle />
<Spacer /> <Spacer />
<CanvasEntityDeleteButton /> <CanvasEntityDeleteButton />
</CanvasEntityHeader> </CanvasEntityHeader>
</CanvasEntityContainer> </CanvasEntityContainer>
</EntityLayerAdapterProviderGate> </EntityLayerAdapterGate>
</EntityIdentifierContext.Provider> </EntityIdentifierContext.Provider>
); );
}); });

@ -3,11 +3,12 @@ import { CanvasEntityContainer } from 'features/controlLayers/components/common/
import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton'; import { CanvasEntityDeleteButton } from 'features/controlLayers/components/common/CanvasEntityDeleteButton';
import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle'; import { CanvasEntityEnabledToggle } from 'features/controlLayers/components/common/CanvasEntityEnabledToggle';
import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader'; import { CanvasEntityHeader } from 'features/controlLayers/components/common/CanvasEntityHeader';
import { CanvasEntityPreviewImage } from 'features/controlLayers/components/common/CanvasEntityPreviewImage';
import { CanvasEntityEditableTitle } from 'features/controlLayers/components/common/CanvasEntityTitleEdit'; import { CanvasEntityEditableTitle } from 'features/controlLayers/components/common/CanvasEntityTitleEdit';
import { RegionalGuidanceBadges } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceBadges'; import { RegionalGuidanceBadges } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceBadges';
import { RegionalGuidanceSettings } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceSettings'; import { RegionalGuidanceSettings } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceSettings';
import { EntityMaskAdapterGate } from 'features/controlLayers/contexts/EntityAdapterContext';
import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext'; import { EntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import { EntityMaskAdapterProviderGate } from 'features/controlLayers/hooks/useEntityMaskAdapter';
import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types'; import type { CanvasEntityIdentifier } from 'features/controlLayers/store/types';
import { memo, useMemo } from 'react'; import { memo, useMemo } from 'react';
@ -23,9 +24,10 @@ export const RegionalGuidance = memo(({ id }: Props) => {
return ( return (
<EntityIdentifierContext.Provider value={entityIdentifier}> <EntityIdentifierContext.Provider value={entityIdentifier}>
<EntityMaskAdapterProviderGate> <EntityMaskAdapterGate>
<CanvasEntityContainer> <CanvasEntityContainer>
<CanvasEntityHeader> <CanvasEntityHeader>
<CanvasEntityPreviewImage />
<CanvasEntityEnabledToggle /> <CanvasEntityEnabledToggle />
<CanvasEntityEditableTitle /> <CanvasEntityEditableTitle />
<Spacer /> <Spacer />
@ -36,7 +38,7 @@ export const RegionalGuidance = memo(({ id }: Props) => {
</CanvasEntityHeader> </CanvasEntityHeader>
<RegionalGuidanceSettings /> <RegionalGuidanceSettings />
</CanvasEntityContainer> </CanvasEntityContainer>
</EntityMaskAdapterProviderGate> </EntityMaskAdapterGate>
</EntityIdentifierContext.Provider> </EntityIdentifierContext.Provider>
); );
}); });

@ -7,13 +7,7 @@ export const CanvasSettingsRecalculateRectsButton = memo(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const canvasManager = useCanvasManager(); const canvasManager = useCanvasManager();
const onClick = useCallback(() => { const onClick = useCallback(() => {
const adapters = [ for (const adapter of canvasManager.adapters.getAll()) {
...canvasManager.rasterLayerAdapters.values(),
...canvasManager.controlLayerAdapters.values(),
...canvasManager.regionalGuidanceAdapters.values(),
...canvasManager.inpaintMaskAdapters.values(),
];
for (const adapter of adapters) {
adapter.transformer.requestRectCalculation(); adapter.transformer.requestRectCalculation();
} }
}, [canvasManager]); }, [canvasManager]);

@ -21,7 +21,6 @@ export const CanvasEntityContainer = memo((props: PropsWithChildren) => {
return ( return (
<Flex <Flex
position="relative" // necessary for drop overlay
flexDir="column" flexDir="column"
w="full" w="full"
bg={isSelected ? 'base.800' : 'base.850'} bg={isSelected ? 'base.800' : 'base.850'}

@ -53,7 +53,7 @@ export const CanvasEntityHeader = memo(({ children, ...rest }: FlexProps) => {
return ( return (
<ContextMenu renderMenu={renderMenu}> <ContextMenu renderMenu={renderMenu}>
{(ref) => ( {(ref) => (
<Flex ref={ref} gap={2} alignItems="center" p={2} {...rest}> <Flex ref={ref} h={16} gap={2} alignItems="center" p={2} {...rest}>
{children} {children}
</Flex> </Flex>
)} )}

@ -0,0 +1,59 @@
import { Box, chakra, Flex } from '@invoke-ai/ui-library';
import { useStore } from '@nanostores/react';
import { useEntityAdapter } from 'features/controlLayers/contexts/EntityAdapterContext';
import { TRANSPARENCY_CHECKER_PATTERN } from 'features/controlLayers/konva/constants';
import { memo, useEffect, useRef } from 'react';
const ChakraCanvas = chakra.canvas;
export const CanvasEntityPreviewImage = memo(() => {
const adapter = useEntityAdapter();
const containerRef = useRef<HTMLDivElement>(null);
const canvasRef = useRef<HTMLCanvasElement>(null);
const cache = useStore(adapter.renderer.$canvasCache);
useEffect(() => {
if (!cache || !canvasRef.current || !containerRef.current) {
return;
}
const ctx = canvasRef.current.getContext('2d');
if (!ctx) {
return;
}
const { rect, canvas } = cache;
ctx.clearRect(0, 0, canvasRef.current.width, canvasRef.current.height);
canvasRef.current.width = rect.width;
canvasRef.current.height = rect.height;
ctx.drawImage(canvas, rect.x, rect.y, rect.width, rect.height, 0, 0, rect.width, rect.height);
}, [adapter.transformer, adapter.transformer.nodeRect, adapter.transformer.pixelRect, cache]);
return (
<Flex
position="relative"
ref={containerRef}
alignItems="center"
justifyContent="center"
w={12}
h={12}
borderRadius="sm"
borderWidth={1}
bg="base.900"
>
<Box
position="absolute"
top={0}
right={0}
bottom={0}
left={0}
bgImage={TRANSPARENCY_CHECKER_PATTERN}
bgSize="5px"
opacity={0.1}
/>
<ChakraCanvas ref={canvasRef} objectFit="contain" maxW="full" maxH="full" />
</Flex>
);
});
CanvasEntityPreviewImage.displayName = 'CanvasEntityPreviewImage';

@ -0,0 +1,82 @@
import type { SyncableMap } from 'common/util/SyncableMap/SyncableMap';
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import type { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLayerAdapter';
import type { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter';
import type { PropsWithChildren } from 'react';
import { createContext, memo, useContext, useMemo, useSyncExternalStore } from 'react';
import { assert } from 'tsafe';
const EntityAdapterContext = createContext<CanvasLayerAdapter | CanvasMaskAdapter | null>(null);
export const EntityLayerAdapterGate = memo(({ children }: PropsWithChildren) => {
const canvasManager = useCanvasManager();
const entityIdentifier = useEntityIdentifierContext();
const store = useMemo<SyncableMap<string, CanvasLayerAdapter>>(() => {
if (entityIdentifier.type === 'raster_layer') {
return canvasManager.adapters.rasterLayers;
}
if (entityIdentifier.type === 'control_layer') {
return canvasManager.adapters.controlLayers;
}
assert(false, 'Unknown entity type');
}, [canvasManager.adapters.controlLayers, canvasManager.adapters.rasterLayers, entityIdentifier.type]);
const adapters = useSyncExternalStore(store.subscribe, store.getSnapshot);
const adapter = useMemo(() => {
return adapters.get(entityIdentifier.id) ?? null;
}, [adapters, entityIdentifier.id]);
if (!adapter) {
return null;
}
return <EntityAdapterContext.Provider value={adapter}>{children}</EntityAdapterContext.Provider>;
});
EntityLayerAdapterGate.displayName = 'EntityLayerAdapterGate';
export const useEntityLayerAdapter = (): CanvasLayerAdapter => {
const adapter = useContext(EntityAdapterContext);
assert(adapter, 'useEntityLayerAdapter must be used within a EntityLayerAdapterGate');
assert(adapter.type === 'layer_adapter', 'useEntityLayerAdapter must be used with a layer adapter');
return adapter;
};
export const EntityMaskAdapterGate = memo(({ children }: PropsWithChildren) => {
const canvasManager = useCanvasManager();
const entityIdentifier = useEntityIdentifierContext();
const store = useMemo<SyncableMap<string, CanvasMaskAdapter>>(() => {
if (entityIdentifier.type === 'inpaint_mask') {
return canvasManager.adapters.inpaintMasks;
}
if (entityIdentifier.type === 'regional_guidance') {
return canvasManager.adapters.regionMasks;
}
assert(false, 'Unknown entity type');
}, [canvasManager.adapters.inpaintMasks, canvasManager.adapters.regionMasks, entityIdentifier.type]);
const adapters = useSyncExternalStore(store.subscribe, store.getSnapshot);
const adapter = useMemo(() => {
return adapters.get(entityIdentifier.id) ?? null;
}, [adapters, entityIdentifier.id]);
if (!adapter) {
return null;
}
return <EntityAdapterContext.Provider value={adapter}>{children}</EntityAdapterContext.Provider>;
});
EntityMaskAdapterGate.displayName = 'EntityMaskAdapterGate';
export const useEntityMaskAdapter = (): CanvasMaskAdapter => {
const adapter = useContext(EntityAdapterContext);
assert(adapter, 'useEntityMaskAdapter must be used within a CanvasMaskAdapterGate');
assert(adapter.type === 'mask_adapter', 'useEntityMaskAdapter must be used with a mask adapter');
return adapter;
};
export const useEntityAdapter = (): CanvasLayerAdapter | CanvasMaskAdapter => {
const adapter = useContext(EntityAdapterContext);
assert(adapter, 'useEntityAdapter must be used within a CanvasRasterLayerAdapterGate');
return adapter;
};

@ -12,7 +12,7 @@ export const useEntityAdapter = (entityIdentifier: CanvasEntityIdentifier): Canv
const entity = canvasManager.stateApi.getEntity(entityIdentifier); const entity = canvasManager.stateApi.getEntity(entityIdentifier);
assert(entity, 'Entity adapter not found'); assert(entity, 'Entity adapter not found');
return entity.adapter; return entity.adapter;
}, [canvasManager, entityIdentifier]); }, [canvasManager.stateApi, entityIdentifier]);
return adapter; return adapter;
}; };

@ -1,37 +0,0 @@
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import type { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLayerAdapter';
import type { PropsWithChildren } from 'react';
import { createContext, memo, useContext, useMemo } from 'react';
import { assert } from 'tsafe';
const EntityLayerAdapterContext = createContext<CanvasLayerAdapter | null>(null);
export const EntityLayerAdapterProviderGate = memo(({ children }: PropsWithChildren) => {
const entityIdentifier = useEntityIdentifierContext();
const canvasManager = useCanvasManager();
const adapter = useMemo(() => {
if (entityIdentifier.type === 'raster_layer') {
return canvasManager.rasterLayerAdapters.get(entityIdentifier.id) ?? null;
} else if (entityIdentifier.type === 'control_layer') {
return canvasManager.controlLayerAdapters.get(entityIdentifier.id) ?? null;
}
assert(false, 'EntityLayerAdapterProviderGate must be used with a valid EntityIdentifierContext');
}, [canvasManager, entityIdentifier]);
if (!canvasManager) {
return null;
}
return <EntityLayerAdapterContext.Provider value={adapter}>{children}</EntityLayerAdapterContext.Provider>;
});
EntityLayerAdapterProviderGate.displayName = 'EntityLayerAdapterProviderGate';
export const useEntityLayerAdapter = (): CanvasLayerAdapter => {
const adapter = useContext(EntityLayerAdapterContext);
assert(adapter, 'useEntityLayerAdapter must be used within a EntityLayerAdapterProviderGate');
return adapter;
};

@ -1,37 +0,0 @@
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
import type { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter';
import type { PropsWithChildren } from 'react';
import { createContext, memo, useContext, useMemo } from 'react';
import { assert } from 'tsafe';
const EntityMaskAdapterContext = createContext<CanvasMaskAdapter | null>(null);
export const EntityMaskAdapterProviderGate = memo(({ children }: PropsWithChildren) => {
const entityIdentifier = useEntityIdentifierContext();
const canvasManager = useCanvasManager();
const adapter = useMemo(() => {
if (entityIdentifier.type === 'inpaint_mask') {
return canvasManager.inpaintMaskAdapters.get(entityIdentifier.id) ?? null;
} else if (entityIdentifier.type === 'regional_guidance') {
return canvasManager.regionalGuidanceAdapters.get(entityIdentifier.id) ?? null;
}
assert(false, 'EntityMaskAdapterProviderGate must be used with a valid EntityIdentifierContext');
}, [canvasManager, entityIdentifier]);
if (!canvasManager) {
return null;
}
return <EntityMaskAdapterContext.Provider value={adapter}>{children}</EntityMaskAdapterContext.Provider>;
});
EntityMaskAdapterProviderGate.displayName = 'EntityMaskAdapterProviderGate';
export const useEntityMaskAdapter = (): CanvasMaskAdapter => {
const adapter = useContext(EntityMaskAdapterContext);
assert(adapter, 'useEntityMaskAdapter must be used within a EntityLayerAdapterProviderGate');
return adapter;
};

@ -30,7 +30,7 @@ export class CanvasCompositorModule {
getCompositeRasterLayerEntityIds = (): string[] => { getCompositeRasterLayerEntityIds = (): string[] => {
const ids = []; const ids = [];
for (const adapter of this.manager.rasterLayerAdapters.values()) { for (const adapter of this.manager.adapters.rasterLayers.values()) {
if (adapter.state.isEnabled && adapter.renderer.hasObjects()) { if (adapter.state.isEnabled && adapter.renderer.hasObjects()) {
ids.push(adapter.id); ids.push(adapter.id);
} }
@ -40,7 +40,7 @@ export class CanvasCompositorModule {
getCompositeInpaintMaskEntityIds = (): string[] => { getCompositeInpaintMaskEntityIds = (): string[] => {
const ids = []; const ids = [];
for (const adapter of this.manager.inpaintMaskAdapters.values()) { for (const adapter of this.manager.adapters.inpaintMasks.values()) {
if (adapter.state.isEnabled && adapter.renderer.hasObjects()) { if (adapter.state.isEnabled && adapter.renderer.hasObjects()) {
ids.push(adapter.id); ids.push(adapter.id);
} }
@ -67,7 +67,7 @@ export class CanvasCompositorModule {
assert(ctx !== null, 'Canvas 2D context is null'); assert(ctx !== null, 'Canvas 2D context is null');
for (const id of this.getCompositeRasterLayerEntityIds()) { for (const id of this.getCompositeRasterLayerEntityIds()) {
const adapter = this.manager.rasterLayerAdapters.get(id); const adapter = this.manager.adapters.rasterLayers.get(id);
if (!adapter) { if (!adapter) {
this.log.warn({ id }, 'Raster layer adapter not found'); this.log.warn({ id }, 'Raster layer adapter not found');
continue; continue;
@ -99,7 +99,7 @@ export class CanvasCompositorModule {
assert(ctx !== null); assert(ctx !== null);
for (const id of this.getCompositeInpaintMaskEntityIds()) { for (const id of this.getCompositeInpaintMaskEntityIds()) {
const adapter = this.manager.inpaintMaskAdapters.get(id); const adapter = this.manager.adapters.inpaintMasks.get(id);
if (!adapter) { if (!adapter) {
this.log.warn({ id }, 'Inpaint mask adapter not found'); this.log.warn({ id }, 'Inpaint mask adapter not found');
continue; continue;
@ -117,7 +117,7 @@ export class CanvasCompositorModule {
extra, extra,
}; };
for (const id of this.getCompositeRasterLayerEntityIds()) { for (const id of this.getCompositeRasterLayerEntityIds()) {
const adapter = this.manager.rasterLayerAdapters.get(id); const adapter = this.manager.adapters.rasterLayers.get(id);
if (!adapter) { if (!adapter) {
this.log.warn({ id }, 'Raster layer adapter not found'); this.log.warn({ id }, 'Raster layer adapter not found');
continue; continue;
@ -132,7 +132,7 @@ export class CanvasCompositorModule {
extra, extra,
}; };
for (const id of this.getCompositeInpaintMaskEntityIds()) { for (const id of this.getCompositeInpaintMaskEntityIds()) {
const adapter = this.manager.inpaintMaskAdapters.get(id); const adapter = this.manager.adapters.inpaintMasks.get(id);
if (!adapter) { if (!adapter) {
this.log.warn({ id }, 'Inpaint mask adapter not found'); this.log.warn({ id }, 'Inpaint mask adapter not found');
continue; continue;

@ -9,6 +9,7 @@ import { CanvasRenderingModule } from 'features/controlLayers/konva/CanvasRender
import { CanvasStageModule } from 'features/controlLayers/konva/CanvasStageModule'; import { CanvasStageModule } from 'features/controlLayers/konva/CanvasStageModule';
import { CanvasWorkerModule } from 'features/controlLayers/konva/CanvasWorkerModule.js'; import { CanvasWorkerModule } from 'features/controlLayers/konva/CanvasWorkerModule.js';
import { getPrefixedId } from 'features/controlLayers/konva/util'; import { getPrefixedId } from 'features/controlLayers/konva/util';
import { SyncableMap } from 'common/util/SyncableMap/SyncableMap';
import type Konva from 'konva'; import type Konva from 'konva';
import { atom } from 'nanostores'; import { atom } from 'nanostores';
import type { Logger } from 'roarr'; import type { Logger } from 'roarr';
@ -32,10 +33,20 @@ export class CanvasManager {
store: AppStore; store: AppStore;
socket: AppSocket; socket: AppSocket;
rasterLayerAdapters: Map<string, CanvasLayerAdapter> = new Map(); adapters = {
controlLayerAdapters: Map<string, CanvasLayerAdapter> = new Map(); rasterLayers: new SyncableMap<string, CanvasLayerAdapter>(),
regionalGuidanceAdapters: Map<string, CanvasMaskAdapter> = new Map(); controlLayers: new SyncableMap<string, CanvasLayerAdapter>(),
inpaintMaskAdapters: Map<string, CanvasMaskAdapter> = new Map(); regionMasks: new SyncableMap<string, CanvasMaskAdapter>(),
inpaintMasks: new SyncableMap<string, CanvasMaskAdapter>(),
getAll: (): (CanvasLayerAdapter | CanvasMaskAdapter)[] => {
return [
...this.adapters.rasterLayers.values(),
...this.adapters.controlLayers.values(),
...this.adapters.regionMasks.values(),
...this.adapters.inpaintMasks.values(),
];
},
};
stateApi: CanvasStateApiModule; stateApi: CanvasStateApiModule;
preview: CanvasPreviewModule; preview: CanvasPreviewModule;
@ -105,13 +116,7 @@ export class CanvasManager {
return () => { return () => {
this.log.debug('Cleaning up canvas manager'); this.log.debug('Cleaning up canvas manager');
const allAdapters = [ for (const adapter of this.adapters.getAll()) {
...this.rasterLayerAdapters.values(),
...this.controlLayerAdapters.values(),
...this.inpaintMaskAdapters.values(),
...this.regionalGuidanceAdapters.values(),
];
for (const adapter of allAdapters) {
adapter.destroy(); adapter.destroy();
} }
this.background.destroy(); this.background.destroy();
@ -148,9 +153,9 @@ export class CanvasManager {
logDebugInfo() { logDebugInfo() {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(this); console.log(this);
for (const layer of this.rasterLayerAdapters.values()) { for (const adapter of this.adapters.getAll()) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log(layer); console.log(adapter);
} }
} }

@ -27,6 +27,7 @@ import type {
import { imageDTOToImageObject } from 'features/controlLayers/store/types'; import { imageDTOToImageObject } from 'features/controlLayers/store/types';
import Konva from 'konva'; import Konva from 'konva';
import type { GroupConfig } from 'konva/lib/Group'; import type { GroupConfig } from 'konva/lib/Group';
import { atom } from 'nanostores';
import type { Logger } from 'roarr'; import type { Logger } from 'roarr';
import { getImageDTO, uploadImage } from 'services/api/endpoints/images'; import { getImageDTO, uploadImage } from 'services/api/endpoints/images';
import type { ImageDTO } from 'services/api/types'; import type { ImageDTO } from 'services/api/types';
@ -118,6 +119,8 @@ export class CanvasObjectRenderer {
} | null; } | null;
}; };
$canvasCache = atom<{ canvas: HTMLCanvasElement; rect: Rect } | null>(null);
constructor(parent: CanvasLayerAdapter | CanvasMaskAdapter) { constructor(parent: CanvasLayerAdapter | CanvasMaskAdapter) {
this.id = getPrefixedId(this.type); this.id = getPrefixedId(this.type);
this.parent = parent; this.parent = parent;
@ -205,7 +208,10 @@ export class CanvasObjectRenderer {
} else if (force || !this.konva.objectGroup.isCached()) { } else if (force || !this.konva.objectGroup.isCached()) {
this.log.trace('Caching object group'); this.log.trace('Caching object group');
this.konva.objectGroup.clearCache(); this.konva.objectGroup.clearCache();
this.konva.objectGroup.cache(); this.konva.objectGroup.cache({ pixelRatio: 1 });
if (!this.parent.transformer.isPendingRectCalculation) {
this.parent.renderer.updatePreviewCanvas();
}
} }
}; };
@ -530,6 +536,24 @@ export class CanvasObjectRenderer {
return imageDTO; return imageDTO;
}; };
updatePreviewCanvas = () => {
if (this.parent.transformer.pixelRect.width === 0 || this.parent.transformer.pixelRect.height === 0) {
return;
}
const canvas = this.konva.objectGroup._getCachedSceneCanvas()._canvas as HTMLCanvasElement | undefined | null;
if (canvas) {
const nodeRect = this.parent.transformer.nodeRect;
const pixelRect = this.parent.transformer.pixelRect;
const rect = {
x: pixelRect.x - nodeRect.x,
y: pixelRect.y - nodeRect.y,
width: pixelRect.width,
height: pixelRect.height,
};
this.$canvasCache.set({ rect, canvas });
}
};
cloneObjectGroup = (attrs?: GroupConfig): Konva.Group => { cloneObjectGroup = (attrs?: GroupConfig): Konva.Group => {
const clone = this.konva.objectGroup.clone(); const clone = this.konva.objectGroup.clone();
clone.cache(); clone.cache();

@ -68,25 +68,27 @@ export class CanvasRenderingModule {
}; };
renderRasterLayers = async (state: CanvasV2State, prevState: CanvasV2State | null) => { renderRasterLayers = async (state: CanvasV2State, prevState: CanvasV2State | null) => {
const adapterMap = this.manager.adapters.rasterLayers;
if (!prevState || state.rasterLayers.isHidden !== prevState.rasterLayers.isHidden) { if (!prevState || state.rasterLayers.isHidden !== prevState.rasterLayers.isHidden) {
for (const adapter of this.manager.rasterLayerAdapters.values()) { for (const adapter of adapterMap.values()) {
adapter.renderer.updateOpacity(state.rasterLayers.isHidden ? 0 : adapter.state.opacity); adapter.renderer.updateOpacity(state.rasterLayers.isHidden ? 0 : adapter.state.opacity);
} }
} }
if (!prevState || state.rasterLayers.entities !== prevState.rasterLayers.entities) { if (!prevState || state.rasterLayers.entities !== prevState.rasterLayers.entities) {
for (const entityAdapter of this.manager.rasterLayerAdapters.values()) { for (const entityAdapter of adapterMap.values()) {
if (!state.rasterLayers.entities.find((l) => l.id === entityAdapter.id)) { if (!state.rasterLayers.entities.find((l) => l.id === entityAdapter.id)) {
await entityAdapter.destroy(); await entityAdapter.destroy();
this.manager.rasterLayerAdapters.delete(entityAdapter.id); adapterMap.delete(entityAdapter.id);
} }
} }
for (const entityState of state.rasterLayers.entities) { for (const entityState of state.rasterLayers.entities) {
let adapter = this.manager.rasterLayerAdapters.get(entityState.id); let adapter = adapterMap.get(entityState.id);
if (!adapter) { if (!adapter) {
adapter = new CanvasLayerAdapter(entityState, this.manager); adapter = new CanvasLayerAdapter(entityState, this.manager);
this.manager.rasterLayerAdapters.set(adapter.id, adapter); adapterMap.set(adapter.id, adapter);
this.manager.stage.addLayer(adapter.konva.layer); this.manager.stage.addLayer(adapter.konva.layer);
} }
await adapter.update({ await adapter.update({
@ -99,25 +101,27 @@ export class CanvasRenderingModule {
}; };
renderControlLayers = async (prevState: CanvasV2State | null, state: CanvasV2State) => { renderControlLayers = async (prevState: CanvasV2State | null, state: CanvasV2State) => {
const adapterMap = this.manager.adapters.controlLayers;
if (!prevState || state.controlLayers.isHidden !== prevState.controlLayers.isHidden) { if (!prevState || state.controlLayers.isHidden !== prevState.controlLayers.isHidden) {
for (const adapter of this.manager.controlLayerAdapters.values()) { for (const adapter of adapterMap.values()) {
adapter.renderer.updateOpacity(state.controlLayers.isHidden ? 0 : adapter.state.opacity); adapter.renderer.updateOpacity(state.controlLayers.isHidden ? 0 : adapter.state.opacity);
} }
} }
if (!prevState || state.controlLayers.entities !== prevState.controlLayers.entities) { if (!prevState || state.controlLayers.entities !== prevState.controlLayers.entities) {
for (const entityAdapter of this.manager.controlLayerAdapters.values()) { for (const entityAdapter of adapterMap.values()) {
if (!state.controlLayers.entities.find((l) => l.id === entityAdapter.id)) { if (!state.controlLayers.entities.find((l) => l.id === entityAdapter.id)) {
await entityAdapter.destroy(); await entityAdapter.destroy();
this.manager.controlLayerAdapters.delete(entityAdapter.id); adapterMap.delete(entityAdapter.id);
} }
} }
for (const entityState of state.controlLayers.entities) { for (const entityState of state.controlLayers.entities) {
let adapter = this.manager.controlLayerAdapters.get(entityState.id); let adapter = adapterMap.get(entityState.id);
if (!adapter) { if (!adapter) {
adapter = new CanvasLayerAdapter(entityState, this.manager); adapter = new CanvasLayerAdapter(entityState, this.manager);
this.manager.controlLayerAdapters.set(adapter.id, adapter); adapterMap.set(adapter.id, adapter);
this.manager.stage.addLayer(adapter.konva.layer); this.manager.stage.addLayer(adapter.konva.layer);
} }
await adapter.update({ await adapter.update({
@ -130,8 +134,10 @@ export class CanvasRenderingModule {
}; };
renderRegionalGuidance = async (prevState: CanvasV2State | null, state: CanvasV2State) => { renderRegionalGuidance = async (prevState: CanvasV2State | null, state: CanvasV2State) => {
const adapterMap = this.manager.adapters.regionMasks;
if (!prevState || state.regions.isHidden !== prevState.regions.isHidden) { if (!prevState || state.regions.isHidden !== prevState.regions.isHidden) {
for (const adapter of this.manager.regionalGuidanceAdapters.values()) { for (const adapter of adapterMap.values()) {
adapter.renderer.updateOpacity(state.regions.isHidden ? 0 : adapter.state.opacity); adapter.renderer.updateOpacity(state.regions.isHidden ? 0 : adapter.state.opacity);
} }
} }
@ -143,18 +149,18 @@ export class CanvasRenderingModule {
state.selectedEntityIdentifier?.id !== prevState.selectedEntityIdentifier?.id state.selectedEntityIdentifier?.id !== prevState.selectedEntityIdentifier?.id
) { ) {
// Destroy the konva nodes for nonexistent entities // Destroy the konva nodes for nonexistent entities
for (const canvasRegion of this.manager.regionalGuidanceAdapters.values()) { for (const canvasRegion of adapterMap.values()) {
if (!state.regions.entities.find((rg) => rg.id === canvasRegion.id)) { if (!state.regions.entities.find((rg) => rg.id === canvasRegion.id)) {
canvasRegion.destroy(); canvasRegion.destroy();
this.manager.regionalGuidanceAdapters.delete(canvasRegion.id); adapterMap.delete(canvasRegion.id);
} }
} }
for (const entityState of state.regions.entities) { for (const entityState of state.regions.entities) {
let adapter = this.manager.regionalGuidanceAdapters.get(entityState.id); let adapter = adapterMap.get(entityState.id);
if (!adapter) { if (!adapter) {
adapter = new CanvasMaskAdapter(entityState, this.manager); adapter = new CanvasMaskAdapter(entityState, this.manager);
this.manager.regionalGuidanceAdapters.set(adapter.id, adapter); adapterMap.set(adapter.id, adapter);
this.manager.stage.addLayer(adapter.konva.layer); this.manager.stage.addLayer(adapter.konva.layer);
} }
await adapter.update({ await adapter.update({
@ -167,8 +173,10 @@ export class CanvasRenderingModule {
}; };
renderInpaintMasks = async (state: CanvasV2State, prevState: CanvasV2State | null) => { renderInpaintMasks = async (state: CanvasV2State, prevState: CanvasV2State | null) => {
const adapterMap = this.manager.adapters.inpaintMasks;
if (!prevState || state.inpaintMasks.isHidden !== prevState.inpaintMasks.isHidden) { if (!prevState || state.inpaintMasks.isHidden !== prevState.inpaintMasks.isHidden) {
for (const adapter of this.manager.inpaintMaskAdapters.values()) { for (const adapter of adapterMap.values()) {
adapter.renderer.updateOpacity(state.inpaintMasks.isHidden ? 0 : adapter.state.opacity); adapter.renderer.updateOpacity(state.inpaintMasks.isHidden ? 0 : adapter.state.opacity);
} }
} }
@ -180,18 +188,18 @@ export class CanvasRenderingModule {
state.selectedEntityIdentifier?.id !== prevState.selectedEntityIdentifier?.id state.selectedEntityIdentifier?.id !== prevState.selectedEntityIdentifier?.id
) { ) {
// Destroy the konva nodes for nonexistent entities // Destroy the konva nodes for nonexistent entities
for (const adapter of this.manager.inpaintMaskAdapters.values()) { for (const adapter of adapterMap.values()) {
if (!state.inpaintMasks.entities.find((rg) => rg.id === adapter.id)) { if (!state.inpaintMasks.entities.find((rg) => rg.id === adapter.id)) {
adapter.destroy(); adapter.destroy();
this.manager.inpaintMaskAdapters.delete(adapter.id); adapterMap.delete(adapter.id);
} }
} }
for (const entityState of state.inpaintMasks.entities) { for (const entityState of state.inpaintMasks.entities) {
let adapter = this.manager.inpaintMaskAdapters.get(entityState.id); let adapter = adapterMap.get(entityState.id);
if (!adapter) { if (!adapter) {
adapter = new CanvasMaskAdapter(entityState, this.manager); adapter = new CanvasMaskAdapter(entityState, this.manager);
this.manager.inpaintMaskAdapters.set(adapter.id, adapter); adapterMap.set(adapter.id, adapter);
this.manager.stage.addLayer(adapter.konva.layer); this.manager.stage.addLayer(adapter.konva.layer);
} }
await adapter.update({ await adapter.update({
@ -239,19 +247,19 @@ export class CanvasRenderingModule {
this.manager.background.konva.layer.zIndex(++zIndex); this.manager.background.konva.layer.zIndex(++zIndex);
for (const { id } of this.manager.stateApi.getRasterLayersState().entities) { for (const { id } of this.manager.stateApi.getRasterLayersState().entities) {
this.manager.rasterLayerAdapters.get(id)?.konva.layer.zIndex(++zIndex); this.manager.adapters.rasterLayers.get(id)?.konva.layer.zIndex(++zIndex);
} }
for (const { id } of this.manager.stateApi.getControlLayersState().entities) { for (const { id } of this.manager.stateApi.getControlLayersState().entities) {
this.manager.controlLayerAdapters.get(id)?.konva.layer.zIndex(++zIndex); this.manager.adapters.controlLayers.get(id)?.konva.layer.zIndex(++zIndex);
} }
for (const { id } of this.manager.stateApi.getRegionsState().entities) { for (const { id } of this.manager.stateApi.getRegionsState().entities) {
this.manager.regionalGuidanceAdapters.get(id)?.konva.layer.zIndex(++zIndex); this.manager.adapters.regionMasks.get(id)?.konva.layer.zIndex(++zIndex);
} }
for (const { id } of this.manager.stateApi.getInpaintMasksState().entities) { for (const { id } of this.manager.stateApi.getInpaintMasksState().entities) {
this.manager.inpaintMaskAdapters.get(id)?.konva.layer.zIndex(++zIndex); this.manager.adapters.inpaintMasks.get(id)?.konva.layer.zIndex(++zIndex);
} }
this.manager.preview.getLayer().zIndex(++zIndex); this.manager.preview.getLayer().zIndex(++zIndex);

@ -58,25 +58,7 @@ export class CanvasStageModule {
getVisibleRect = (): Rect => { getVisibleRect = (): Rect => {
const rects = []; const rects = [];
for (const adapter of this.manager.inpaintMaskAdapters.values()) { for (const adapter of this.manager.adapters.getAll()) {
if (adapter.state.isEnabled) {
rects.push(adapter.transformer.getRelativeRect());
}
}
for (const adapter of this.manager.rasterLayerAdapters.values()) {
if (adapter.state.isEnabled) {
rects.push(adapter.transformer.getRelativeRect());
}
}
for (const adapter of this.manager.controlLayerAdapters.values()) {
if (adapter.state.isEnabled) {
rects.push(adapter.transformer.getRelativeRect());
}
}
for (const adapter of this.manager.regionalGuidanceAdapters.values()) {
if (adapter.state.isEnabled) { if (adapter.state.isEnabled) {
rects.push(adapter.transformer.getRelativeRect()); rects.push(adapter.transformer.getRelativeRect());
} }

@ -174,16 +174,16 @@ export class CanvasStateApiModule {
if (identifier.type === 'raster_layer') { if (identifier.type === 'raster_layer') {
entityState = state.rasterLayers.entities.find((i) => i.id === identifier.id) ?? null; entityState = state.rasterLayers.entities.find((i) => i.id === identifier.id) ?? null;
entityAdapter = this.manager.rasterLayerAdapters.get(identifier.id) ?? null; entityAdapter = this.manager.adapters.rasterLayers.get(identifier.id) ?? null;
} else if (identifier.type === 'control_layer') { } else if (identifier.type === 'control_layer') {
entityState = state.controlLayers.entities.find((i) => i.id === identifier.id) ?? null; entityState = state.controlLayers.entities.find((i) => i.id === identifier.id) ?? null;
entityAdapter = this.manager.controlLayerAdapters.get(identifier.id) ?? null; entityAdapter = this.manager.adapters.controlLayers.get(identifier.id) ?? null;
} else if (identifier.type === 'regional_guidance') { } else if (identifier.type === 'regional_guidance') {
entityState = state.regions.entities.find((i) => i.id === identifier.id) ?? null; entityState = state.regions.entities.find((i) => i.id === identifier.id) ?? null;
entityAdapter = this.manager.regionalGuidanceAdapters.get(identifier.id) ?? null; entityAdapter = this.manager.adapters.regionMasks.get(identifier.id) ?? null;
} else if (identifier.type === 'inpaint_mask') { } else if (identifier.type === 'inpaint_mask') {
entityState = state.inpaintMasks.entities.find((i) => i.id === identifier.id) ?? null; entityState = state.inpaintMasks.entities.find((i) => i.id === identifier.id) ?? null;
entityAdapter = this.manager.inpaintMaskAdapters.get(identifier.id) ?? null; entityAdapter = this.manager.adapters.inpaintMasks.get(identifier.id) ?? null;
} }
if (entityState && entityAdapter) { if (entityState && entityAdapter) {

@ -496,7 +496,7 @@ export class CanvasTransformer {
startTransform = () => { startTransform = () => {
this.log.debug('Starting transform'); this.log.debug('Starting transform');
this.isTransforming = true; this.isTransforming = true;
this.manager.stateApi.setTool('move') this.manager.stateApi.setTool('move');
// When transforming, we want the stage to still be movable if the view tool is selected. If the transformer or // When transforming, we want the stage to still be movable if the view tool is selected. If the transformer or
// interaction rect are listening, it will interrupt the stage's drag events. So we should disable listening // interaction rect are listening, it will interrupt the stage's drag events. So we should disable listening
// when the view tool is selected // when the view tool is selected
@ -605,6 +605,7 @@ export class CanvasTransformer {
if (this.isPendingRectCalculation) { if (this.isPendingRectCalculation) {
this.syncInteractionState(); this.syncInteractionState();
this.parent.renderer.updatePreviewCanvas();
return; return;
} }
@ -615,6 +616,7 @@ export class CanvasTransformer {
// The layer is fully transparent but has objects - reset it // The layer is fully transparent but has objects - reset it
this.manager.stateApi.resetEntity({ entityIdentifier: this.parent.getEntityIdentifier() }); this.manager.stateApi.resetEntity({ entityIdentifier: this.parent.getEntityIdentifier() });
this.syncInteractionState(); this.syncInteractionState();
this.parent.renderer.updatePreviewCanvas();
return; return;
} }
@ -628,6 +630,7 @@ export class CanvasTransformer {
}; };
this.parent.renderer.konva.objectGroup.setAttrs(groupAttrs); this.parent.renderer.konva.objectGroup.setAttrs(groupAttrs);
this.parent.renderer.konva.bufferGroup.setAttrs(groupAttrs); this.parent.renderer.konva.bufferGroup.setAttrs(groupAttrs);
this.parent.renderer.updatePreviewCanvas();
}; };
calculateRect = debounce(() => { calculateRect = debounce(() => {

@ -23,7 +23,7 @@ export const addControlAdapters = async (
.filter((layer) => isValidControlAdapter(layer.controlAdapter, base)); .filter((layer) => isValidControlAdapter(layer.controlAdapter, base));
for (const layer of validControlLayers) { for (const layer of validControlLayers) {
const adapter = manager.controlLayerAdapters.get(layer.id); const adapter = manager.adapters.controlLayers.get(layer.id);
assert(adapter, 'Adapter not found'); assert(adapter, 'Adapter not found');
const imageDTO = await adapter.renderer.rasterize({ rect: bbox, attrs: { opacity: 1, filters: [] } }); const imageDTO = await adapter.renderer.rasterize({ rect: bbox, attrs: { opacity: 1, filters: [] } });
if (layer.controlAdapter.type === 'controlnet') { if (layer.controlAdapter.type === 'controlnet') {

@ -47,7 +47,7 @@ export const addRegions = async (
const validRegions = regions.filter((rg) => isValidRegion(rg, base)); const validRegions = regions.filter((rg) => isValidRegion(rg, base));
for (const region of validRegions) { for (const region of validRegions) {
const adapter = manager.regionalGuidanceAdapters.get(region.id); const adapter = manager.adapters.regionMasks.get(region.id);
assert(adapter, 'Adapter not found'); assert(adapter, 'Adapter not found');
const imageDTO = await adapter.renderer.rasterize({ rect: bbox }); const imageDTO = await adapter.renderer.rasterize({ rect: bbox });