mirror of
https://github.com/invoke-ai/InvokeAI.git
synced 2026-04-23 03:00:31 -04:00
feat(ui): filter behaviour
- Add `reset` functionality - Rename badly named `autoPreviewFilter` to `autoProcessFilter` - Do not process filter when starting, unless `autoProcessFilter` is enabled
This commit is contained in:
@@ -1657,7 +1657,6 @@
|
||||
"storeNotInitialized": "Store is not initialized"
|
||||
},
|
||||
"controlLayers": {
|
||||
"autoPreviewFilter": "Auto Preview",
|
||||
"bookmark": "Bookmark for Quick Switch",
|
||||
"fitBboxToLayers": "Fit Bbox To Layers",
|
||||
"removeBookmark": "Remove Bookmark",
|
||||
@@ -1794,7 +1793,9 @@
|
||||
"filter": "Filter",
|
||||
"filters": "Filters",
|
||||
"filterType": "Filter Type",
|
||||
"preview": "Preview",
|
||||
"autoProcess": "Auto Process",
|
||||
"reset": "Reset",
|
||||
"process": "Process",
|
||||
"apply": "Apply",
|
||||
"cancel": "Cancel",
|
||||
"spandrel": {
|
||||
|
||||
@@ -7,20 +7,20 @@ import { useCanvasManager } from 'features/controlLayers/contexts/CanvasManagerP
|
||||
import type { CanvasEntityAdapterControlLayer } from 'features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterControlLayer';
|
||||
import type { CanvasEntityAdapterRasterLayer } from 'features/controlLayers/konva/CanvasEntity/CanvasEntityAdapterRasterLayer';
|
||||
import {
|
||||
selectAutoPreviewFilter,
|
||||
settingsAutoPreviewFilterToggled,
|
||||
selectAutoProcessFilter,
|
||||
settingsAutoProcessFilterToggled,
|
||||
} from 'features/controlLayers/store/canvasSettingsSlice';
|
||||
import { type FilterConfig, IMAGE_FILTERS } from 'features/controlLayers/store/types';
|
||||
import { memo, useCallback, useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { PiCheckBold, PiShootingStarBold, PiXBold } from 'react-icons/pi';
|
||||
import { PiArrowsCounterClockwiseBold, PiCheckBold, PiShootingStarBold, PiXBold } from 'react-icons/pi';
|
||||
|
||||
const FilterBox = memo(({ adapter }: { adapter: CanvasEntityAdapterRasterLayer | CanvasEntityAdapterControlLayer }) => {
|
||||
const { t } = useTranslation();
|
||||
const dispatch = useAppDispatch();
|
||||
const config = useStore(adapter.filterer.$filterConfig);
|
||||
const isProcessing = useStore(adapter.filterer.$isProcessing);
|
||||
const autoPreviewFilter = useAppSelector(selectAutoPreviewFilter);
|
||||
const autoProcessFilter = useAppSelector(selectAutoProcessFilter);
|
||||
|
||||
const onChangeFilterConfig = useCallback(
|
||||
(filterConfig: FilterConfig) => {
|
||||
@@ -36,8 +36,8 @@ const FilterBox = memo(({ adapter }: { adapter: CanvasEntityAdapterRasterLayer |
|
||||
[adapter.filterer.$filterConfig]
|
||||
);
|
||||
|
||||
const onChangeAutoPreviewFilter = useCallback(() => {
|
||||
dispatch(settingsAutoPreviewFilterToggled());
|
||||
const onChangeAutoProcessFilter = useCallback(() => {
|
||||
dispatch(settingsAutoProcessFilterToggled());
|
||||
}, [dispatch]);
|
||||
|
||||
const isValid = useMemo(() => {
|
||||
@@ -63,8 +63,8 @@ const FilterBox = memo(({ adapter }: { adapter: CanvasEntityAdapterRasterLayer |
|
||||
</Heading>
|
||||
<Spacer />
|
||||
<FormControl w="min-content">
|
||||
<FormLabel m={0}>{t('controlLayers.autoPreviewFilter')}</FormLabel>
|
||||
<Switch size="sm" isChecked={autoPreviewFilter} onChange={onChangeAutoPreviewFilter} />
|
||||
<FormLabel m={0}>{t('controlLayers.filter.autoProcess')}</FormLabel>
|
||||
<Switch size="sm" isChecked={autoProcessFilter} onChange={onChangeAutoProcessFilter} />
|
||||
</FormControl>
|
||||
</Flex>
|
||||
<FilterTypeSelect filterType={config.type} onChange={onChangeFilterType} />
|
||||
@@ -73,18 +73,27 @@ const FilterBox = memo(({ adapter }: { adapter: CanvasEntityAdapterRasterLayer |
|
||||
<Button
|
||||
variant="ghost"
|
||||
leftIcon={<PiShootingStarBold />}
|
||||
onClick={adapter.filterer.previewFilter}
|
||||
onClick={adapter.filterer.process}
|
||||
isLoading={isProcessing}
|
||||
loadingText={t('controlLayers.filter.preview')}
|
||||
loadingText={t('controlLayers.filter.process')}
|
||||
isDisabled={!isValid}
|
||||
>
|
||||
{t('controlLayers.filter.preview')}
|
||||
{t('controlLayers.filter.process')}
|
||||
</Button>
|
||||
<Spacer />
|
||||
<Button
|
||||
leftIcon={<PiArrowsCounterClockwiseBold />}
|
||||
onClick={adapter.filterer.reset}
|
||||
isLoading={isProcessing}
|
||||
loadingText={t('controlLayers.filter.reset')}
|
||||
variant="ghost"
|
||||
>
|
||||
{t('controlLayers.filter.reset')}
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
leftIcon={<PiCheckBold />}
|
||||
onClick={adapter.filterer.applyFilter}
|
||||
onClick={adapter.filterer.apply}
|
||||
isLoading={isProcessing}
|
||||
loadingText={t('controlLayers.filter.apply')}
|
||||
isDisabled={!isValid}
|
||||
@@ -94,7 +103,7 @@ const FilterBox = memo(({ adapter }: { adapter: CanvasEntityAdapterRasterLayer |
|
||||
<Button
|
||||
variant="ghost"
|
||||
leftIcon={<PiXBold />}
|
||||
onClick={adapter.filterer.cancelFilter}
|
||||
onClick={adapter.filterer.cancel}
|
||||
isLoading={isProcessing}
|
||||
loadingText={t('controlLayers.filter.cancel')}
|
||||
isDisabled={!isValid}
|
||||
|
||||
@@ -68,7 +68,7 @@ export const useEntityFilter = (entityIdentifier: CanvasEntityIdentifier | null)
|
||||
if (!adapter) {
|
||||
return;
|
||||
}
|
||||
adapter.filterer.startFilter();
|
||||
adapter.filterer.start();
|
||||
}, [isDisabled, entityIdentifier, canvasManager]);
|
||||
|
||||
return { isDisabled, start } as const;
|
||||
|
||||
@@ -231,7 +231,7 @@ export abstract class CanvasEntityAdapterBase<
|
||||
}
|
||||
this.transformer.destroy();
|
||||
if (this.filterer?.$isFiltering.get()) {
|
||||
this.filterer.cancelFilter();
|
||||
this.filterer.cancel();
|
||||
}
|
||||
this.konva.layer.destroy();
|
||||
this.manager.deleteAdapter(this.entityIdentifier);
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { CanvasEntityAdapterRasterLayer } from 'features/controlLayers/konv
|
||||
import type { CanvasManager } from 'features/controlLayers/konva/CanvasManager';
|
||||
import { CanvasModuleBase } from 'features/controlLayers/konva/CanvasModuleBase';
|
||||
import { getPrefixedId } from 'features/controlLayers/konva/util';
|
||||
import { selectAutoPreviewFilter } from 'features/controlLayers/store/canvasSettingsSlice';
|
||||
import { selectAutoProcessFilter } from 'features/controlLayers/store/canvasSettingsSlice';
|
||||
import type { CanvasImageState, FilterConfig } from 'features/controlLayers/store/types';
|
||||
import { IMAGE_FILTERS, imageDTOToImageObject } from 'features/controlLayers/store/types';
|
||||
import { debounce } from 'lodash-es';
|
||||
@@ -41,31 +41,33 @@ export class CanvasEntityFilterer extends CanvasModuleBase {
|
||||
|
||||
this.subscriptions.add(
|
||||
this.$filterConfig.listen(() => {
|
||||
if (this.manager.stateApi.getSettings().autoPreviewFilter && this.$isFiltering.get()) {
|
||||
this.previewFilter();
|
||||
if (this.manager.stateApi.getSettings().autoProcessFilter && this.$isFiltering.get()) {
|
||||
this.process();
|
||||
}
|
||||
})
|
||||
);
|
||||
this.subscriptions.add(
|
||||
this.manager.stateApi.createStoreSubscription(selectAutoPreviewFilter, (autoPreviewFilter) => {
|
||||
this.manager.stateApi.createStoreSubscription(selectAutoProcessFilter, (autoPreviewFilter) => {
|
||||
if (autoPreviewFilter && this.$isFiltering.get()) {
|
||||
this.previewFilter();
|
||||
this.process();
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
startFilter = (config?: FilterConfig) => {
|
||||
start = (config?: FilterConfig) => {
|
||||
this.log.trace('Initializing filter');
|
||||
if (config) {
|
||||
this.$filterConfig.set(config);
|
||||
}
|
||||
this.$isFiltering.set(true);
|
||||
this.manager.stateApi.$filteringAdapter.set(this.parent);
|
||||
this.previewFilter();
|
||||
if (this.manager.stateApi.getSettings().autoProcessFilter) {
|
||||
this.process();
|
||||
}
|
||||
};
|
||||
|
||||
previewFilter = debounce(
|
||||
process = debounce(
|
||||
async () => {
|
||||
const config = this.$filterConfig.get();
|
||||
const isValid = IMAGE_FILTERS[config.type].validateConfig?.(config as never) ?? true;
|
||||
@@ -113,7 +115,7 @@ export class CanvasEntityFilterer extends CanvasModuleBase {
|
||||
{ leading: true, trailing: true }
|
||||
);
|
||||
|
||||
applyFilter = () => {
|
||||
apply = () => {
|
||||
const imageState = this.imageState;
|
||||
if (!imageState) {
|
||||
this.log.warn('No image state to apply filter to');
|
||||
@@ -138,14 +140,20 @@ export class CanvasEntityFilterer extends CanvasModuleBase {
|
||||
this.manager.stateApi.$filteringAdapter.set(null);
|
||||
};
|
||||
|
||||
cancelFilter = () => {
|
||||
this.log.trace('Cancelling filter');
|
||||
reset = () => {
|
||||
this.log.trace('Resetting filter');
|
||||
|
||||
this.parent.bufferRenderer.clearBuffer();
|
||||
this.parent.renderer.showObjects();
|
||||
this.parent.transformer.updatePosition();
|
||||
this.parent.renderer.syncCache(true);
|
||||
this.imageState = null;
|
||||
};
|
||||
|
||||
cancel = () => {
|
||||
this.log.trace('Cancelling filter');
|
||||
|
||||
this.reset();
|
||||
this.$isProcessing.set(false);
|
||||
this.$isFiltering.set(false);
|
||||
this.manager.stateApi.$filteringAdapter.set(null);
|
||||
|
||||
@@ -53,9 +53,9 @@ type CanvasSettingsState = {
|
||||
*/
|
||||
compositeMaskedRegions: boolean;
|
||||
/**
|
||||
* Whether to automatically preview the filter when the filter configuration changes.
|
||||
* Whether to automatically process the filter when the filter configuration changes.
|
||||
*/
|
||||
autoPreviewFilter: boolean;
|
||||
autoProcessFilter: boolean;
|
||||
/**
|
||||
* The snap-to-grid setting for the canvas.
|
||||
*/
|
||||
@@ -77,7 +77,7 @@ const initialState: CanvasSettingsState = {
|
||||
color: { r: 31, g: 160, b: 224, a: 1 }, // invokeBlue.500
|
||||
sendToCanvas: false,
|
||||
compositeMaskedRegions: false,
|
||||
autoPreviewFilter: true,
|
||||
autoProcessFilter: true,
|
||||
gridSize: 64,
|
||||
};
|
||||
|
||||
@@ -115,8 +115,8 @@ export const canvasSettingsSlice = createSlice({
|
||||
settingsCompositeMaskedRegionsChanged: (state, action: PayloadAction<boolean>) => {
|
||||
state.compositeMaskedRegions = action.payload;
|
||||
},
|
||||
settingsAutoPreviewFilterToggled: (state) => {
|
||||
state.autoPreviewFilter = !state.autoPreviewFilter;
|
||||
settingsAutoProcessFilterToggled: (state) => {
|
||||
state.autoProcessFilter = !state.autoProcessFilter;
|
||||
},
|
||||
settingsGridSizeChanged: (state, action: PayloadAction<GridSize>) => {
|
||||
state.gridSize = action.payload;
|
||||
@@ -135,7 +135,7 @@ export const {
|
||||
settingsInvertScrollForToolWidthChanged,
|
||||
settingsSendToCanvasChanged,
|
||||
settingsCompositeMaskedRegionsChanged,
|
||||
settingsAutoPreviewFilterToggled,
|
||||
settingsAutoProcessFilterToggled,
|
||||
settingsGridSizeChanged,
|
||||
} = canvasSettingsSlice.actions;
|
||||
|
||||
@@ -158,5 +158,5 @@ const createCanvasSettingsSelector = <T>(selector: Selector<CanvasSettingsState,
|
||||
export const selectAutoSave = createCanvasSettingsSelector((settings) => settings.autoSave);
|
||||
export const selectDynamicGrid = createCanvasSettingsSelector((settings) => settings.dynamicGrid);
|
||||
export const selectShowHUD = createCanvasSettingsSelector((settings) => settings.showHUD);
|
||||
export const selectAutoPreviewFilter = createCanvasSettingsSelector((settings) => settings.autoPreviewFilter);
|
||||
export const selectAutoProcessFilter = createCanvasSettingsSelector((settings) => settings.autoProcessFilter);
|
||||
export const selectGridSize = createCanvasSettingsSelector((settings) => settings.gridSize);
|
||||
|
||||
Reference in New Issue
Block a user