Skip to content

Commit 254b89b

Browse files
dunkeronipsychedelicious
authored andcommitted
add: Blur filter option on canvas
1 parent 2b122d7 commit 254b89b

File tree

5 files changed

+122
-0
lines changed

5 files changed

+122
-0
lines changed

invokeai/frontend/web/public/locales/en.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1932,6 +1932,14 @@
19321932
"description": "Generates an edge map from the selected layer using the PiDiNet edge detection model.",
19331933
"scribble": "Scribble",
19341934
"quantize_edges": "Quantize Edges"
1935+
},
1936+
"img_blur": {
1937+
"label": "Blur Image",
1938+
"description": "Blurs the selected layer.",
1939+
"blur_type": "Blur Type",
1940+
"blur_radius": "Radius",
1941+
"gaussian_type": "Gaussian",
1942+
"box_type": "Box"
19351943
}
19361944
},
19371945
"transform": {
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import type { ComboboxOnChange } from '@invoke-ai/ui-library';
2+
import { Combobox, CompositeNumberInput, CompositeSlider, FormControl, FormLabel } from '@invoke-ai/ui-library';
3+
import type { BlurFilterConfig, BlurTypes } from 'features/controlLayers/store/filters';
4+
import { IMAGE_FILTERS, isBlurTypes } from 'features/controlLayers/store/filters';
5+
import { memo, useCallback, useMemo } from 'react';
6+
import { useTranslation } from 'react-i18next';
7+
8+
import type { FilterComponentProps } from './types';
9+
10+
type Props = FilterComponentProps<BlurFilterConfig>;
11+
const DEFAULTS = IMAGE_FILTERS.img_blur.buildDefaults();
12+
13+
export const FilterBlur = memo(({ onChange, config }: Props) => {
14+
const { t } = useTranslation();
15+
const handleBlurTypeChange = useCallback<ComboboxOnChange>(
16+
(v) => {
17+
if (!isBlurTypes(v?.value)) {
18+
return;
19+
}
20+
onChange({ ...config, blur_type: v.value });
21+
},
22+
[config, onChange]
23+
);
24+
25+
const handleRadiusChange = useCallback(
26+
(v: number) => {
27+
onChange({ ...config, radius: v });
28+
},
29+
[config, onChange]
30+
);
31+
32+
const options: { label: string; value: BlurTypes }[] = useMemo(
33+
() => [
34+
{ label: t('controlLayers.filter.img_blur.gaussian_type'), value: 'gaussian' },
35+
{ label: t('controlLayers.filter.img_blur.box_type'), value: 'box' },
36+
],
37+
[t]
38+
);
39+
40+
const value = useMemo(() => options.filter((o) => o.value === config.blur_type)[0], [options, config.blur_type]);
41+
42+
return (
43+
<>
44+
<FormControl>
45+
<FormLabel m={0}>{t('controlLayers.filter.img_blur.blur_type')}</FormLabel>
46+
<Combobox value={value} options={options} onChange={handleBlurTypeChange} isSearchable={false} />
47+
</FormControl>
48+
<FormControl>
49+
<FormLabel m={0}>{t('controlLayers.filter.img_blur.blur_radius')}</FormLabel>
50+
<CompositeSlider
51+
value={config.radius}
52+
defaultValue={DEFAULTS.radius}
53+
onChange={handleRadiusChange}
54+
min={1}
55+
max={64}
56+
step={1}
57+
marks
58+
/>
59+
<CompositeNumberInput
60+
value={config.radius}
61+
defaultValue={DEFAULTS.radius}
62+
onChange={handleRadiusChange}
63+
min={1}
64+
max={4096}
65+
step={1}
66+
/>
67+
</FormControl>
68+
</>
69+
);
70+
});
71+
72+
FilterBlur.displayName = 'FilterBlur';

invokeai/frontend/web/src/features/controlLayers/components/Filters/FilterSettings.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { IAINoContentFallback } from 'common/components/IAIImageFallback';
2+
import { FilterBlur } from 'features/controlLayers/components/Filters/FilterBlur';
23
import { FilterCannyEdgeDetection } from 'features/controlLayers/components/Filters/FilterCannyEdgeDetection';
34
import { FilterColorMap } from 'features/controlLayers/components/Filters/FilterColorMap';
45
import { FilterContentShuffle } from 'features/controlLayers/components/Filters/FilterContentShuffle';
@@ -19,6 +20,10 @@ type Props = { filterConfig: FilterConfig; onChange: (filterConfig: FilterConfig
1920
export const FilterSettings = memo(({ filterConfig, onChange }: Props) => {
2021
const { t } = useTranslation();
2122

23+
if (filterConfig.type === 'img_blur') {
24+
return <FilterBlur config={filterConfig} onChange={onChange} />;
25+
}
26+
2227
if (filterConfig.type === 'canny_edge_detection') {
2328
return <FilterCannyEdgeDetection config={filterConfig} onChange={onChange} />;
2429
}

invokeai/frontend/web/src/features/controlLayers/store/filters.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,16 @@ const zSpandrelFilterConfig = z.object({
9595
});
9696
export type SpandrelFilterConfig = z.infer<typeof zSpandrelFilterConfig>;
9797

98+
const zBlurTypes = z.enum(['gaussian', 'box']);
99+
export type BlurTypes = z.infer<typeof zBlurTypes>;
100+
export const isBlurTypes = (v: unknown): v is BlurTypes => zBlurTypes.safeParse(v).success;
101+
const zBlurFilterConfig = z.object({
102+
type: z.literal('img_blur'),
103+
blur_type: zBlurTypes,
104+
radius: z.number().gte(0),
105+
});
106+
export type BlurFilterConfig = z.infer<typeof zBlurFilterConfig>;
107+
98108
const zFilterConfig = z.discriminatedUnion('type', [
99109
zCannyEdgeDetectionFilterConfig,
100110
zColorMapFilterConfig,
@@ -109,6 +119,7 @@ const zFilterConfig = z.discriminatedUnion('type', [
109119
zPiDiNetEdgeDetectionFilterConfig,
110120
zDWOpenposeDetectionFilterConfig,
111121
zSpandrelFilterConfig,
122+
zBlurFilterConfig,
112123
]);
113124
export type FilterConfig = z.infer<typeof zFilterConfig>;
114125

@@ -126,6 +137,7 @@ const zFilterType = z.enum([
126137
'pidi_edge_detection',
127138
'dw_openpose_detection',
128139
'spandrel_filter',
140+
'img_blur',
129141
]);
130142
export type FilterType = z.infer<typeof zFilterType>;
131143
export const isFilterType = (v: unknown): v is FilterType => zFilterType.safeParse(v).success;
@@ -429,6 +441,28 @@ export const IMAGE_FILTERS: { [key in FilterConfig['type']]: ImageFilterData<key
429441
return true;
430442
},
431443
},
444+
img_blur: {
445+
type: 'img_blur',
446+
buildDefaults: () => ({
447+
type: 'img_blur',
448+
blur_type: 'gaussian',
449+
radius: 8,
450+
}),
451+
buildGraph: ({ image_name }, { blur_type, radius }) => {
452+
const graph = new Graph(getPrefixedId('img_blur'));
453+
const node = graph.addNode({
454+
id: getPrefixedId('img_blur'),
455+
type: 'img_blur',
456+
image: { image_name },
457+
blur_type: blur_type,
458+
radius: radius,
459+
});
460+
return {
461+
graph,
462+
outputNodeId: node.id,
463+
};
464+
},
465+
},
432466
} as const;
433467

434468
/**

invokeai/frontend/web/src/features/controlLayers/store/types.test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type {
2+
BlurFilterConfig,
23
CannyEdgeDetectionFilterConfig,
34
ColorMapFilterConfig,
45
ContentShuffleFilterConfig,
@@ -54,6 +55,7 @@ describe('Control Adapter Types', () => {
5455
});
5556
test('Processor Configs', () => {
5657
// Types derived from OpenAPI
58+
type _BlurFilterConfig = Required<Pick<Invocation<'img_blur'>, 'type' | 'radius' | 'blur_type'>>;
5759
type _CannyEdgeDetectionFilterConfig = Required<
5860
Pick<Invocation<'canny_edge_detection'>, 'type' | 'low_threshold' | 'high_threshold'>
5961
>;
@@ -81,6 +83,7 @@ describe('Control Adapter Types', () => {
8183

8284
// The processor configs are manually modeled zod schemas. This test ensures that the inferred types are correct.
8385
// The types prefixed with `_` are types generated from OpenAPI, while the types without the prefix are manually modeled.
86+
assert<Equals<_BlurFilterConfig, BlurFilterConfig>>();
8487
assert<Equals<_CannyEdgeDetectionFilterConfig, CannyEdgeDetectionFilterConfig>>();
8588
assert<Equals<_ColorMapFilterConfig, ColorMapFilterConfig>>();
8689
assert<Equals<_ContentShuffleFilterConfig, ContentShuffleFilterConfig>>();

0 commit comments

Comments
 (0)