mirror of
https://github.com/invoke-ai/InvokeAI
synced 2024-08-30 20:32:17 +00:00
feat(ui): revise filter implementation
This commit is contained in:
parent
dd1dcb5eba
commit
09f1aac3a3
@ -6,20 +6,23 @@ import { InpaintMaskList } from 'features/controlLayers/components/InpaintMask/I
|
|||||||
import { IPAdapterList } from 'features/controlLayers/components/IPAdapter/IPAdapterList';
|
import { IPAdapterList } from 'features/controlLayers/components/IPAdapter/IPAdapterList';
|
||||||
import { RasterLayerEntityList } from 'features/controlLayers/components/RasterLayer/RasterLayerEntityList';
|
import { RasterLayerEntityList } from 'features/controlLayers/components/RasterLayer/RasterLayerEntityList';
|
||||||
import { RegionalGuidanceEntityList } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceEntityList';
|
import { RegionalGuidanceEntityList } from 'features/controlLayers/components/RegionalGuidance/RegionalGuidanceEntityList';
|
||||||
|
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||||
import { memo } from 'react';
|
import { memo } from 'react';
|
||||||
|
|
||||||
export const CanvasEntityList = memo(() => {
|
export const CanvasEntityList = memo(() => {
|
||||||
return (
|
return (
|
||||||
<ScrollableContent>
|
<CanvasManagerProviderGate>
|
||||||
<Flex flexDir="column" gap={4} pt={2} data-testid="control-layers-layer-list">
|
<ScrollableContent>
|
||||||
<CanvasEntityOpacity />
|
<Flex flexDir="column" gap={4} pt={2} data-testid="control-layers-layer-list">
|
||||||
<InpaintMaskList />
|
<CanvasEntityOpacity />
|
||||||
<RegionalGuidanceEntityList />
|
<InpaintMaskList />
|
||||||
<IPAdapterList />
|
<RegionalGuidanceEntityList />
|
||||||
<ControlLayerEntityList />
|
<IPAdapterList />
|
||||||
<RasterLayerEntityList />
|
<ControlLayerEntityList />
|
||||||
</Flex>
|
<RasterLayerEntityList />
|
||||||
</ScrollableContent>
|
</Flex>
|
||||||
|
</ScrollableContent>
|
||||||
|
</CanvasManagerProviderGate>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { Combobox, FormControl, Tooltip } from '@invoke-ai/ui-library';
|
import { Combobox, FormControl, Tooltip } from '@invoke-ai/ui-library';
|
||||||
import { useAppSelector } from 'app/store/storeHooks';
|
import { useAppSelector } from 'app/store/storeHooks';
|
||||||
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
|
import { useGroupedModelCombobox } from 'common/hooks/useGroupedModelCombobox';
|
||||||
|
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
import { $filterConfig, $filteringEntity } from 'features/controlLayers/store/canvasV2Slice';
|
|
||||||
import { IMAGE_FILTERS, isFilterType } from 'features/controlLayers/store/types';
|
import { IMAGE_FILTERS, isFilterType } from 'features/controlLayers/store/types';
|
||||||
import { memo, useCallback, useMemo } from 'react';
|
import { memo, useCallback, useMemo } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -17,6 +17,7 @@ type Props = {
|
|||||||
export const ControlLayerControlAdapterModel = memo(({ modelKey, onChange: onChangeModel }: Props) => {
|
export const ControlLayerControlAdapterModel = memo(({ modelKey, onChange: onChangeModel }: Props) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const entityIdentifier = useEntityIdentifierContext();
|
const entityIdentifier = useEntityIdentifierContext();
|
||||||
|
const canvasManager = useCanvasManager();
|
||||||
const currentBaseModel = useAppSelector((s) => s.canvasV2.params.model?.base);
|
const currentBaseModel = useAppSelector((s) => s.canvasV2.params.model?.base);
|
||||||
const [modelConfigs, { isLoading }] = useControlNetAndT2IAdapterModels();
|
const [modelConfigs, { isLoading }] = useControlNetAndT2IAdapterModels();
|
||||||
const selectedModel = useMemo(() => modelConfigs.find((m) => m.key === modelKey), [modelConfigs, modelKey]);
|
const selectedModel = useMemo(() => modelConfigs.find((m) => m.key === modelKey), [modelConfigs, modelKey]);
|
||||||
@ -35,19 +36,21 @@ export const ControlLayerControlAdapterModel = memo(({ modelKey, onChange: onCha
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update the filter, preferring the model's default
|
|
||||||
if (isFilterType(modelConfig.default_settings?.preprocessor)) {
|
|
||||||
$filterConfig.set(IMAGE_FILTERS[modelConfig.default_settings.preprocessor].buildDefaults(modelConfig.base));
|
|
||||||
} else {
|
|
||||||
$filterConfig.set(IMAGE_FILTERS.canny_image_processor.buildDefaults(modelConfig.base));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Open the filter popup by setting this entity as the filtering entity
|
// Open the filter popup by setting this entity as the filtering entity
|
||||||
if (!$filteringEntity.get()) {
|
if (!canvasManager.filter.$adapter.get()) {
|
||||||
$filteringEntity.set(entityIdentifier);
|
// Update the filter, preferring the model's default
|
||||||
|
if (isFilterType(modelConfig.default_settings?.preprocessor)) {
|
||||||
|
canvasManager.filter.$config.set(
|
||||||
|
IMAGE_FILTERS[modelConfig.default_settings.preprocessor].buildDefaults(modelConfig.base)
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
canvasManager.filter.$config.set(IMAGE_FILTERS.canny_image_processor.buildDefaults(modelConfig.base));
|
||||||
|
}
|
||||||
|
canvasManager.filter.initialize(entityIdentifier);
|
||||||
|
canvasManager.filter.previewFilter();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[entityIdentifier, modelKey, onChangeModel]
|
[canvasManager.filter, entityIdentifier, modelKey, onChangeModel]
|
||||||
);
|
);
|
||||||
|
|
||||||
const getIsDisabled = useCallback(
|
const getIsDisabled = useCallback(
|
||||||
|
@ -6,6 +6,7 @@ import { ControlLayersToolbar } from 'features/controlLayers/components/ControlL
|
|||||||
import { Filter } from 'features/controlLayers/components/Filters/Filter';
|
import { Filter } from 'features/controlLayers/components/Filters/Filter';
|
||||||
import { StageComponent } from 'features/controlLayers/components/StageComponent';
|
import { StageComponent } from 'features/controlLayers/components/StageComponent';
|
||||||
import { StagingAreaToolbar } from 'features/controlLayers/components/StagingArea/StagingAreaToolbar';
|
import { StagingAreaToolbar } from 'features/controlLayers/components/StagingArea/StagingAreaToolbar';
|
||||||
|
import { CanvasManagerProviderGate } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||||
import { memo, useRef } from 'react';
|
import { memo, useRef } from 'react';
|
||||||
|
|
||||||
export const CanvasEditor = memo(() => {
|
export const CanvasEditor = memo(() => {
|
||||||
@ -33,7 +34,9 @@ export const CanvasEditor = memo(() => {
|
|||||||
<StagingAreaToolbar />
|
<StagingAreaToolbar />
|
||||||
</Flex>
|
</Flex>
|
||||||
<Flex position="absolute" bottom={16}>
|
<Flex position="absolute" bottom={16}>
|
||||||
<Filter />
|
<CanvasManagerProviderGate>
|
||||||
|
<Filter />
|
||||||
|
</CanvasManagerProviderGate>
|
||||||
</Flex>
|
</Flex>
|
||||||
<CanvasDropArea />
|
<CanvasDropArea />
|
||||||
</Flex>
|
</Flex>
|
||||||
|
@ -2,8 +2,7 @@ import { Button, ButtonGroup, Flex, Heading } from '@invoke-ai/ui-library';
|
|||||||
import { useStore } from '@nanostores/react';
|
import { useStore } from '@nanostores/react';
|
||||||
import { FilterSettings } from 'features/controlLayers/components/Filters/FilterSettings';
|
import { FilterSettings } from 'features/controlLayers/components/Filters/FilterSettings';
|
||||||
import { FilterTypeSelect } from 'features/controlLayers/components/Filters/FilterTypeSelect';
|
import { FilterTypeSelect } from 'features/controlLayers/components/Filters/FilterTypeSelect';
|
||||||
import { $canvasManager } from 'features/controlLayers/konva/CanvasManager';
|
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||||
import { $filterConfig, $filteringEntity, $isProcessingFilter } from 'features/controlLayers/store/canvasV2Slice';
|
|
||||||
import { type FilterConfig, IMAGE_FILTERS } from 'features/controlLayers/store/types';
|
import { type FilterConfig, IMAGE_FILTERS } from 'features/controlLayers/store/types';
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
@ -11,64 +10,38 @@ import { PiCheckBold, PiShootingStarBold, PiXBold } from 'react-icons/pi';
|
|||||||
|
|
||||||
export const Filter = memo(() => {
|
export const Filter = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const filteringEntity = useStore($filteringEntity);
|
const canvasManager = useCanvasManager();
|
||||||
const filterConfig = useStore($filterConfig);
|
const adapter = useStore(canvasManager.filter.$adapter);
|
||||||
const isProcessingFilter = useStore($isProcessingFilter);
|
const config = useStore(canvasManager.filter.$config);
|
||||||
|
const isProcessing = useStore(canvasManager.filter.$isProcessing);
|
||||||
|
|
||||||
const previewFilter = useCallback(() => {
|
const previewFilter = useCallback(() => {
|
||||||
if (!filteringEntity) {
|
canvasManager.filter.previewFilter();
|
||||||
return;
|
}, [canvasManager.filter]);
|
||||||
}
|
|
||||||
const canvasManager = $canvasManager.get();
|
|
||||||
if (!canvasManager) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const entity = canvasManager.stateApi.getEntity(filteringEntity);
|
|
||||||
if (!entity || (entity.type !== 'raster_layer' && entity.type !== 'control_layer')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
entity.adapter.filter.previewFilter();
|
|
||||||
}, [filteringEntity]);
|
|
||||||
|
|
||||||
const applyFilter = useCallback(() => {
|
const applyFilter = useCallback(() => {
|
||||||
if (!filteringEntity) {
|
canvasManager.filter.applyFilter();
|
||||||
return;
|
}, [canvasManager.filter]);
|
||||||
}
|
|
||||||
const canvasManager = $canvasManager.get();
|
|
||||||
if (!canvasManager) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const entity = canvasManager.stateApi.getEntity(filteringEntity);
|
|
||||||
if (!entity || (entity.type !== 'raster_layer' && entity.type !== 'control_layer')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
entity.adapter.filter.applyFilter();
|
|
||||||
}, [filteringEntity]);
|
|
||||||
|
|
||||||
const cancelFilter = useCallback(() => {
|
const cancelFilter = useCallback(() => {
|
||||||
if (!filteringEntity) {
|
canvasManager.filter.cancelFilter();
|
||||||
return;
|
}, [canvasManager.filter]);
|
||||||
}
|
|
||||||
const canvasManager = $canvasManager.get();
|
|
||||||
if (!canvasManager) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const entity = canvasManager.stateApi.getEntity(filteringEntity);
|
|
||||||
if (!entity || (entity.type !== 'raster_layer' && entity.type !== 'control_layer')) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
entity.adapter.filter.cancelFilter();
|
|
||||||
}, [filteringEntity]);
|
|
||||||
|
|
||||||
const onChangeFilterConfig = useCallback((filterConfig: FilterConfig) => {
|
const onChangeFilterConfig = useCallback(
|
||||||
$filterConfig.set(filterConfig);
|
(filterConfig: FilterConfig) => {
|
||||||
}, []);
|
canvasManager.filter.$config.set(filterConfig);
|
||||||
|
},
|
||||||
|
[canvasManager.filter.$config]
|
||||||
|
);
|
||||||
|
|
||||||
const onChangeFilterType = useCallback((filterType: FilterConfig['type']) => {
|
const onChangeFilterType = useCallback(
|
||||||
$filterConfig.set(IMAGE_FILTERS[filterType].buildDefaults());
|
(filterType: FilterConfig['type']) => {
|
||||||
}, []);
|
canvasManager.filter.$config.set(IMAGE_FILTERS[filterType].buildDefaults());
|
||||||
|
},
|
||||||
|
[canvasManager.filter.$config]
|
||||||
|
);
|
||||||
|
|
||||||
if (!filteringEntity || !filterConfig) {
|
if (!adapter) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,13 +61,13 @@ export const Filter = memo(() => {
|
|||||||
<Heading size="md" color="base.300" userSelect="none">
|
<Heading size="md" color="base.300" userSelect="none">
|
||||||
{t('controlLayers.filter.filter')}
|
{t('controlLayers.filter.filter')}
|
||||||
</Heading>
|
</Heading>
|
||||||
<FilterTypeSelect filterType={filterConfig.type} onChange={onChangeFilterType} />
|
<FilterTypeSelect filterType={config.type} onChange={onChangeFilterType} />
|
||||||
<FilterSettings filterConfig={filterConfig} onChange={onChangeFilterConfig} />
|
<FilterSettings filterConfig={config} onChange={onChangeFilterConfig} />
|
||||||
<ButtonGroup isAttached={false} size="sm" alignSelf="self-end">
|
<ButtonGroup isAttached={false} size="sm" alignSelf="self-end">
|
||||||
<Button
|
<Button
|
||||||
leftIcon={<PiShootingStarBold />}
|
leftIcon={<PiShootingStarBold />}
|
||||||
onClick={previewFilter}
|
onClick={previewFilter}
|
||||||
isLoading={isProcessingFilter}
|
isLoading={isProcessing}
|
||||||
loadingText={t('controlLayers.filter.preview')}
|
loadingText={t('controlLayers.filter.preview')}
|
||||||
>
|
>
|
||||||
{t('controlLayers.filter.preview')}
|
{t('controlLayers.filter.preview')}
|
||||||
@ -102,7 +75,7 @@ export const Filter = memo(() => {
|
|||||||
<Button
|
<Button
|
||||||
leftIcon={<PiCheckBold />}
|
leftIcon={<PiCheckBold />}
|
||||||
onClick={applyFilter}
|
onClick={applyFilter}
|
||||||
isLoading={isProcessingFilter}
|
isLoading={isProcessing}
|
||||||
loadingText={t('controlLayers.filter.apply')}
|
loadingText={t('controlLayers.filter.apply')}
|
||||||
>
|
>
|
||||||
{t('controlLayers.filter.apply')}
|
{t('controlLayers.filter.apply')}
|
||||||
@ -110,7 +83,7 @@ export const Filter = memo(() => {
|
|||||||
<Button
|
<Button
|
||||||
leftIcon={<PiXBold />}
|
leftIcon={<PiXBold />}
|
||||||
onClick={cancelFilter}
|
onClick={cancelFilter}
|
||||||
isLoading={isProcessingFilter}
|
isLoading={isProcessing}
|
||||||
loadingText={t('controlLayers.filter.cancel')}
|
loadingText={t('controlLayers.filter.cancel')}
|
||||||
>
|
>
|
||||||
{t('controlLayers.filter.cancel')}
|
{t('controlLayers.filter.cancel')}
|
||||||
|
@ -1,19 +1,21 @@
|
|||||||
import { MenuItem } from '@invoke-ai/ui-library';
|
import { MenuItem } from '@invoke-ai/ui-library';
|
||||||
|
import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerProviderGate';
|
||||||
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
import { useEntityIdentifierContext } from 'features/controlLayers/contexts/EntityIdentifierContext';
|
||||||
import { $filteringEntity } from 'features/controlLayers/store/canvasV2Slice';
|
|
||||||
import { memo, useCallback } from 'react';
|
import { memo, useCallback } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { PiShootingStarBold } from 'react-icons/pi';
|
import { PiShootingStarBold } from 'react-icons/pi';
|
||||||
|
|
||||||
export const CanvasEntityMenuItemsFilter = memo(() => {
|
export const CanvasEntityMenuItemsFilter = memo(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
const canvasManager = useCanvasManager();
|
||||||
const entityIdentifier = useEntityIdentifierContext();
|
const entityIdentifier = useEntityIdentifierContext();
|
||||||
const filter = useCallback(() => {
|
|
||||||
$filteringEntity.set(entityIdentifier);
|
const onClick = useCallback(() => {
|
||||||
}, [entityIdentifier]);
|
canvasManager.filter.initialize(entityIdentifier);
|
||||||
|
}, [entityIdentifier, canvasManager.filter]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MenuItem onClick={filter} icon={<PiShootingStarBold />}>
|
<MenuItem onClick={onClick} icon={<PiShootingStarBold />}>
|
||||||
{t('controlLayers.filter.filter')}
|
{t('controlLayers.filter.filter')}
|
||||||
</MenuItem>
|
</MenuItem>
|
||||||
);
|
);
|
||||||
|
@ -2,8 +2,9 @@ import type { JSONObject, SerializableObject } from 'common/types';
|
|||||||
import type { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLayerAdapter';
|
import type { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLayerAdapter';
|
||||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||||
import type { CanvasImageState } from 'features/controlLayers/store/types';
|
import type { CanvasEntityIdentifier, CanvasImageState, FilterConfig } from 'features/controlLayers/store/types';
|
||||||
import { IMAGE_FILTERS, imageDTOToImageObject } from 'features/controlLayers/store/types';
|
import { IMAGE_FILTERS, imageDTOToImageObject } from 'features/controlLayers/store/types';
|
||||||
|
import { atom } from 'nanostores';
|
||||||
import type { Logger } from 'roarr';
|
import type { Logger } from 'roarr';
|
||||||
import { getImageDTO } from 'services/api/endpoints/images';
|
import { getImageDTO } from 'services/api/endpoints/images';
|
||||||
import { queueApi } from 'services/api/endpoints/queue';
|
import { queueApi } from 'services/api/endpoints/queue';
|
||||||
@ -18,27 +19,48 @@ export class CanvasFilter {
|
|||||||
|
|
||||||
id: string;
|
id: string;
|
||||||
path: string[];
|
path: string[];
|
||||||
parent: CanvasLayerAdapter;
|
|
||||||
manager: CanvasManager;
|
manager: CanvasManager;
|
||||||
log: Logger;
|
log: Logger;
|
||||||
|
|
||||||
imageState: CanvasImageState | null = null;
|
imageState: CanvasImageState | null = null;
|
||||||
|
|
||||||
constructor(parent: CanvasLayerAdapter) {
|
$adapter = atom<CanvasLayerAdapter | null>(null);
|
||||||
|
$isProcessing = atom<boolean>(false);
|
||||||
|
$config = atom<FilterConfig>(IMAGE_FILTERS.canny_image_processor.buildDefaults());
|
||||||
|
|
||||||
|
constructor(manager: CanvasManager) {
|
||||||
this.id = getPrefixedId(this.type);
|
this.id = getPrefixedId(this.type);
|
||||||
this.parent = parent;
|
this.manager = manager;
|
||||||
this.manager = parent.manager;
|
this.path = this.manager.path.concat(this.id);
|
||||||
this.path = this.parent.path.concat(this.id);
|
|
||||||
this.log = this.manager.buildLogger(this.getLoggingContext);
|
this.log = this.manager.buildLogger(this.getLoggingContext);
|
||||||
this.log.trace('Creating filter');
|
this.log.trace('Creating filter');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initialize = (entityIdentifier: CanvasEntityIdentifier) => {
|
||||||
|
this.log.trace('Initializing filter');
|
||||||
|
const entity = this.manager.stateApi.getEntity(entityIdentifier);
|
||||||
|
if (!entity) {
|
||||||
|
this.log.warn({ entityIdentifier }, 'Unable to find entity');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (entity.type !== 'raster_layer' && entity.type !== 'control_layer') {
|
||||||
|
this.log.warn({ entityIdentifier }, 'Unsupported entity type');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.$adapter.set(entity.adapter);
|
||||||
|
};
|
||||||
|
|
||||||
previewFilter = async () => {
|
previewFilter = async () => {
|
||||||
const config = this.manager.stateApi.$filterConfig.get();
|
const adapter = this.$adapter.get();
|
||||||
|
if (!adapter) {
|
||||||
|
this.log.warn('Cannot preview filter without an adapter');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const config = this.$config.get();
|
||||||
this.log.trace({ config }, 'Previewing filter');
|
this.log.trace({ config }, 'Previewing filter');
|
||||||
const dispatch = this.manager.stateApi._store.dispatch;
|
const dispatch = this.manager.stateApi._store.dispatch;
|
||||||
const rect = this.parent.transformer.getRelativeRect();
|
const rect = adapter.transformer.getRelativeRect();
|
||||||
const imageDTO = await this.parent.renderer.rasterize(rect, false);
|
const imageDTO = await adapter.renderer.rasterize(rect, false);
|
||||||
// TODO(psyche): I can't get TS to be happy, it thinkgs `config` is `never` but it should be inferred from the generic... I'll just cast it for now
|
// TODO(psyche): I can't get TS to be happy, it thinkgs `config` is `never` but it should be inferred from the generic... I'll just cast it for now
|
||||||
const filterNode = IMAGE_FILTERS[config.type].buildNode(imageDTO, config as never);
|
const filterNode = IMAGE_FILTERS[config.type].buildNode(imageDTO, config as never);
|
||||||
const enqueueBatchArg: BatchConfig = {
|
const enqueueBatchArg: BatchConfig = {
|
||||||
@ -76,19 +98,19 @@ export class CanvasFilter {
|
|||||||
assert(imageDTO, "Failed to fetch processor output's image DTO");
|
assert(imageDTO, "Failed to fetch processor output's image DTO");
|
||||||
|
|
||||||
this.imageState = imageDTOToImageObject(imageDTO);
|
this.imageState = imageDTOToImageObject(imageDTO);
|
||||||
this.parent.renderer.clearBuffer();
|
adapter.renderer.clearBuffer();
|
||||||
|
|
||||||
await this.parent.renderer.setBuffer(this.imageState);
|
await adapter.renderer.setBuffer(this.imageState);
|
||||||
|
|
||||||
this.parent.renderer.hideObjects();
|
adapter.renderer.hideObjects();
|
||||||
this.manager.stateApi.$isProcessingFilter.set(false);
|
this.$isProcessing.set(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
this.manager.socket.on('invocation_complete', listener);
|
this.manager.socket.on('invocation_complete', listener);
|
||||||
|
|
||||||
this.log.trace({ enqueueBatchArg } as SerializableObject, 'Enqueuing filter batch');
|
this.log.trace({ enqueueBatchArg } as SerializableObject, 'Enqueuing filter batch');
|
||||||
|
|
||||||
this.manager.stateApi.$isProcessingFilter.set(true);
|
this.$isProcessing.set(true);
|
||||||
dispatch(
|
dispatch(
|
||||||
queueApi.endpoints.enqueueBatch.initiate(enqueueBatchArg, {
|
queueApi.endpoints.enqueueBatch.initiate(enqueueBatchArg, {
|
||||||
fixedCacheKey: 'enqueueBatch',
|
fixedCacheKey: 'enqueueBatch',
|
||||||
@ -97,36 +119,47 @@ export class CanvasFilter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
applyFilter = () => {
|
applyFilter = () => {
|
||||||
this.log.trace('Applying filter');
|
const imageState = this.imageState;
|
||||||
if (!this.imageState) {
|
const adapter = this.$adapter.get();
|
||||||
|
if (!imageState) {
|
||||||
this.log.warn('No image state to apply filter to');
|
this.log.warn('No image state to apply filter to');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.parent.renderer.commitBuffer();
|
if (!adapter) {
|
||||||
const rect = this.parent.transformer.getRelativeRect();
|
this.log.warn('Cannot apply filter without an adapter');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.log.trace('Applying filter');
|
||||||
|
adapter.renderer.commitBuffer();
|
||||||
|
const rect = adapter.transformer.getRelativeRect();
|
||||||
this.manager.stateApi.rasterizeEntity({
|
this.manager.stateApi.rasterizeEntity({
|
||||||
entityIdentifier: this.parent.getEntityIdentifier(),
|
entityIdentifier: adapter.getEntityIdentifier(),
|
||||||
imageObject: this.imageState,
|
imageObject: imageState,
|
||||||
rect: {
|
rect: {
|
||||||
x: Math.round(rect.x),
|
x: Math.round(rect.x),
|
||||||
y: Math.round(rect.y),
|
y: Math.round(rect.y),
|
||||||
width: this.imageState.image.height,
|
width: imageState.image.height,
|
||||||
height: this.imageState.image.width,
|
height: imageState.image.width,
|
||||||
},
|
},
|
||||||
replaceObjects: true,
|
replaceObjects: true,
|
||||||
});
|
});
|
||||||
this.parent.renderer.showObjects();
|
adapter.renderer.showObjects();
|
||||||
this.manager.stateApi.$filteringEntity.set(null);
|
|
||||||
this.imageState = null;
|
this.imageState = null;
|
||||||
|
this.$adapter.set(null);
|
||||||
};
|
};
|
||||||
|
|
||||||
cancelFilter = () => {
|
cancelFilter = () => {
|
||||||
this.log.trace('Cancelling filter');
|
this.log.trace('Cancelling filter');
|
||||||
this.parent.renderer.clearBuffer();
|
|
||||||
this.parent.renderer.showObjects();
|
const adapter = this.$adapter.get();
|
||||||
this.manager.stateApi.$filteringEntity.set(null);
|
|
||||||
|
if (adapter) {
|
||||||
|
adapter.renderer.clearBuffer();
|
||||||
|
adapter.renderer.showObjects();
|
||||||
|
this.$adapter.set(null);
|
||||||
|
}
|
||||||
this.imageState = null;
|
this.imageState = null;
|
||||||
this.manager.stateApi.$isProcessingFilter.set(false);
|
this.$isProcessing.set(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
destroy = () => {
|
destroy = () => {
|
||||||
@ -141,6 +174,6 @@ export class CanvasFilter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
getLoggingContext = (): JSONObject => {
|
getLoggingContext = (): JSONObject => {
|
||||||
return { ...this.parent.getLoggingContext(), path: this.path.join('.') };
|
return { ...this.manager.getLoggingContext(), path: this.path.join('.') };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import type { JSONObject } from 'common/types';
|
import type { JSONObject } from 'common/types';
|
||||||
import { deepClone } from 'common/util/deepClone';
|
import { deepClone } from 'common/util/deepClone';
|
||||||
import { CanvasFilter } from 'features/controlLayers/konva/CanvasFilter';
|
|
||||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||||
import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
|
import { CanvasObjectRenderer } from 'features/controlLayers/konva/CanvasObjectRenderer';
|
||||||
import { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer';
|
import { CanvasTransformer } from 'features/controlLayers/konva/CanvasTransformer';
|
||||||
@ -31,7 +30,6 @@ export class CanvasLayerAdapter {
|
|||||||
};
|
};
|
||||||
transformer: CanvasTransformer;
|
transformer: CanvasTransformer;
|
||||||
renderer: CanvasObjectRenderer;
|
renderer: CanvasObjectRenderer;
|
||||||
filter: CanvasFilter;
|
|
||||||
|
|
||||||
isFirstRender: boolean = true;
|
isFirstRender: boolean = true;
|
||||||
|
|
||||||
@ -56,7 +54,6 @@ export class CanvasLayerAdapter {
|
|||||||
|
|
||||||
this.renderer = new CanvasObjectRenderer(this);
|
this.renderer = new CanvasObjectRenderer(this);
|
||||||
this.transformer = new CanvasTransformer(this);
|
this.transformer = new CanvasTransformer(this);
|
||||||
this.filter = new CanvasFilter(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2,6 +2,7 @@ import type { AppSocket } from 'app/hooks/useSocketIO';
|
|||||||
import { logger } from 'app/logging/logger';
|
import { logger } from 'app/logging/logger';
|
||||||
import type { AppStore } from 'app/store/store';
|
import type { AppStore } from 'app/store/store';
|
||||||
import type { JSONObject } from 'common/types';
|
import type { JSONObject } from 'common/types';
|
||||||
|
import { CanvasFilter } from 'features/controlLayers/konva/CanvasFilter';
|
||||||
import { MAX_CANVAS_SCALE, MIN_CANVAS_SCALE } from 'features/controlLayers/konva/constants';
|
import { MAX_CANVAS_SCALE, MIN_CANVAS_SCALE } from 'features/controlLayers/konva/constants';
|
||||||
import {
|
import {
|
||||||
canvasToBlob,
|
canvasToBlob,
|
||||||
@ -53,6 +54,7 @@ export class CanvasManager {
|
|||||||
stateApi: CanvasStateApi;
|
stateApi: CanvasStateApi;
|
||||||
preview: CanvasPreview;
|
preview: CanvasPreview;
|
||||||
background: CanvasBackground;
|
background: CanvasBackground;
|
||||||
|
filter: CanvasFilter;
|
||||||
|
|
||||||
log: Logger;
|
log: Logger;
|
||||||
socket: AppSocket;
|
socket: AppSocket;
|
||||||
@ -92,6 +94,8 @@ export class CanvasManager {
|
|||||||
this.background = new CanvasBackground(this);
|
this.background = new CanvasBackground(this);
|
||||||
this.stage.add(this.background.konva.layer);
|
this.stage.add(this.background.konva.layer);
|
||||||
|
|
||||||
|
this.filter = new CanvasFilter(this);
|
||||||
|
|
||||||
this._worker.onmessage = (event: MessageEvent<ExtentsResult | WorkerLogMessage>) => {
|
this._worker.onmessage = (event: MessageEvent<ExtentsResult | WorkerLogMessage>) => {
|
||||||
const { type, data } = event.data;
|
const { type, data } = event.data;
|
||||||
if (type === 'log') {
|
if (type === 'log') {
|
||||||
|
@ -4,11 +4,8 @@ import type { CanvasLayerAdapter } from 'features/controlLayers/konva/CanvasLaye
|
|||||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||||
import type { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter';
|
import type { CanvasMaskAdapter } from 'features/controlLayers/konva/CanvasMaskAdapter';
|
||||||
import {
|
import {
|
||||||
$filterConfig,
|
|
||||||
$filteringEntity,
|
|
||||||
$isDrawing,
|
$isDrawing,
|
||||||
$isMouseDown,
|
$isMouseDown,
|
||||||
$isProcessingFilter,
|
|
||||||
$lastAddedPoint,
|
$lastAddedPoint,
|
||||||
$lastCursorPos,
|
$lastCursorPos,
|
||||||
$lastMouseDownPos,
|
$lastMouseDownPos,
|
||||||
@ -233,9 +230,6 @@ export class CanvasStateApi {
|
|||||||
};
|
};
|
||||||
|
|
||||||
$transformingEntity = $transformingEntity;
|
$transformingEntity = $transformingEntity;
|
||||||
$filteringEntity = $filteringEntity;
|
|
||||||
$filterConfig = $filterConfig;
|
|
||||||
$isProcessingFilter = $isProcessingFilter;
|
|
||||||
|
|
||||||
$toolState: WritableAtom<CanvasV2State['tool']> = atom();
|
$toolState: WritableAtom<CanvasV2State['tool']> = atom();
|
||||||
$currentFill: WritableAtom<RgbaColor> = atom();
|
$currentFill: WritableAtom<RgbaColor> = atom();
|
||||||
|
@ -38,10 +38,9 @@ import type {
|
|||||||
EntityMovedPayload,
|
EntityMovedPayload,
|
||||||
EntityRasterizedPayload,
|
EntityRasterizedPayload,
|
||||||
EntityRectAddedPayload,
|
EntityRectAddedPayload,
|
||||||
FilterConfig,
|
|
||||||
StageAttrs,
|
StageAttrs,
|
||||||
} from './types';
|
} from './types';
|
||||||
import { getEntityIdentifier, IMAGE_FILTERS, isDrawableEntity } from './types';
|
import { getEntityIdentifier, isDrawableEntity } from './types';
|
||||||
|
|
||||||
const initialState: CanvasV2State = {
|
const initialState: CanvasV2State = {
|
||||||
_version: 3,
|
_version: 3,
|
||||||
@ -702,9 +701,6 @@ export const $lastMouseDownPos = atom<Coordinate | null>(null);
|
|||||||
export const $lastCursorPos = atom<Coordinate | null>(null);
|
export const $lastCursorPos = atom<Coordinate | null>(null);
|
||||||
export const $spaceKey = atom<boolean>(false);
|
export const $spaceKey = atom<boolean>(false);
|
||||||
export const $transformingEntity = atom<CanvasEntityIdentifier | null>(null);
|
export const $transformingEntity = atom<CanvasEntityIdentifier | null>(null);
|
||||||
export const $filteringEntity = atom<CanvasEntityIdentifier | null>(null);
|
|
||||||
export const $filterConfig = atom<FilterConfig>(IMAGE_FILTERS.canny_image_processor.buildDefaults());
|
|
||||||
export const $isProcessingFilter = atom(false);
|
|
||||||
|
|
||||||
export const canvasV2PersistConfig: PersistConfig<CanvasV2State> = {
|
export const canvasV2PersistConfig: PersistConfig<CanvasV2State> = {
|
||||||
name: canvasV2Slice.name,
|
name: canvasV2Slice.name,
|
||||||
|
Loading…
Reference in New Issue
Block a user