Skip to content

Commit 743611f

Browse files
authored
feat: add support for processing masks in demos (#51)
Closes: #50
1 parent 4f45f5e commit 743611f

23 files changed

+329
-90
lines changed

README.md

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ The documentation is available on [https://image-js-docs.pages.dev/](https://ima
66

77
## Create demos
88

9-
A demo is simply function which takes an image as input and returns an image as output. When imported in `md` files, it will be transformed into a demo component which allows to choose from various image or video sources to showcase the image transformation.
9+
A demo is simply function which takes an image or mask as input and returns an image or mask as output. When imported in `md` files, it will be transformed into a demo component which allows to choose from various image or video sources to showcase the image transformation.
1010

11-
### Example
11+
### Image example
1212

1313
In `docs/demos/gaussian-blur.demo.tsx`, define your demo function.
1414

@@ -20,18 +20,38 @@ export default function blur(image: Image) {
2020
}
2121
```
2222

23-
Then in `docs/blur-filters.md`, import and use the demo component.
23+
### Mask example
24+
25+
In `docs/demos/invert-filter.mask.demo.tsx`, define your demo function.
26+
27+
```ts
28+
import { Mask } from 'image-js';
29+
30+
export default function invert(mask: Mask) {
31+
return mask.invert();
32+
}
33+
```
34+
35+
### Usage in markdown
36+
37+
Then in `docs/page.md`, import and use the demo component.
2438

2539
```markdown
2640
import GaussianBlur from './demos/gaussian-blur.demo.tsx';
41+
import MaskInvert from './demos/invert-filter.mask.demo.tsx';
2742

2843
# Gaussian blur
2944

3045
<GaussianBlur />
46+
47+
# Mask invert
48+
49+
<MaskInvert />
3150
```
3251

3352
### Caveats
3453

35-
1. The file must end with `.demo.tsx` to get processed correctly by the builder. The file extension should be `.tsx`, even if the file does not render any JSX.
36-
2. The file must export a default function, which takes an `Image` as input and returns an `Image` as output.
37-
3. The demo must only import from `image-js`.
54+
1. The file must end with `.demo.tsx` for image filters and `.mask.demo.tsx` for masks to work. The file extension should be `.tsx`, even if the file does not render any JSX.
55+
2. For image demos, the file must export a default function, which takes an `image: Image` as input and returns an `Image` or a `Mask` as output.
56+
3. For mask demos, the file must export a default function, which takes an `image: Mask` as input and returns an `Image` or a `Mask` as output.
57+
4. The demo must only import from `image-js`.

demo-loader.webpack.cjs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,13 +51,15 @@ module.exports = function demoLoader(source) {
5151
);
5252
}
5353

54+
const isMask = this.resourcePath.endsWith('.mask.demo.tsx');
55+
5456
const editorCode = `import * as IJS from 'image-js';
5557
/**
5658
* Process the image
57-
* @param { IJS.Image } image the input image
58-
* @returns { IJS.Image | IJS.Mask } the processed image
59+
* @param { IJS.${isMask ? 'Mask } mask' : 'Image } image'} the input image
60+
* @returns { IJS.Image | IJS.Mask } the processed image or mask
5961
*/
60-
export function process(image) {
62+
export function process(${isMask ? 'mask' : 'image'}) {
6163
${processBody}
6264
}
6365
`;
@@ -86,10 +88,13 @@ export function process(image) {
8688
const defaultEditorCode= \`${editorCode}\`;
8789
export default function Demo(props) {
8890
return (
89-
<ImageDemo code={code} defaultEditorCode={defaultEditorCode} processImage={process} name={name} {...props} />
91+
<ImageDemo code={code} defaultEditorCode={defaultEditorCode} processImage={process} name={name} isMask={${
92+
isMask ? 'true' : 'false'
93+
}} {...props} />
9094
);
9195
}
9296
`;
97+
9398
babel
9499
.transformAsync(modifiedSource, {
95100
filename: this.resourcePath,

docs/Features/Filters/Invert.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import InvertDemo from './invert.demo.tsx'
22

3+
import InvertMaskDemo from './invert.mask.demo.tsx';
4+
35
[Check options and parameters of invert method](https://image-js.github.io/image-js-typescript/classes/Image.html#invert 'github.io link')
46

57
[Invert filter](<https://en.wikipedia.org/wiki/Negative_(photography)> 'wikipedia link on negative filtering') is an image processing technique used to reverse the color values of an image, creating a negative or "inverted" version of the original. In this process, the darkest areas become the lightest, and the lightest areas become the darkest, while the midtones are adjusted accordingly. The invert filter is a simple but effective way to create visual contrast and produce interesting effects.
@@ -33,3 +35,7 @@ ImageJS uses components to calculate each pixel value and leaves alpha channel u
3335
:::
3436

3537
</details>
38+
39+
### On masks
40+
41+
<InvertMaskDemo />
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import { Mask } from 'image-js';
2+
3+
export default function invert(mask: Mask) {
4+
return mask.invert();
5+
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
"eslint": "eslint ./src",
1212
"eslint-fix": "npm run eslint -- --fix",
1313
"prettier": "prettier --check ./",
14-
"prettier-fix": "prettier --write ./",
14+
"prettier-write": "prettier --write ./",
1515
"serve": "docusaurus serve",
1616
"dev": "docusaurus start",
1717
"swizzle": "docusaurus swizzle",

src/components/editor/CodeEditor.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ function MonacoEditor({
4343
setValue: Dispatch<SetStateAction<string>>;
4444
commands?: (editor: Monaco) => EditorCommand[];
4545
}) {
46-
const editorRef = useRef<any>(null);
46+
const editorRef = useRef<unknown>(null);
4747
const monacoRef = useRef<Monaco | null>(null);
4848
const { colorMode } = useColorMode();
4949

src/demo/components/ImageDemo.tsx

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import React, { useEffect } from 'react';
22

3-
import { defaultImages } from '../contexts/demo/defaultImages';
3+
import { defaultImages, defaultMasks } from '../contexts/demo/defaultImages';
44
import { useDemoStateContext } from '../contexts/demo/demoContext';
55
import { useSelectImage } from '../contexts/demo/dispatchHelpers';
66

@@ -15,15 +15,22 @@ export default function ImageDemo({
1515
code,
1616
defaultEditorCode,
1717
noAutoRun,
18+
isMask,
1819
}: {
1920
name: string;
2021
code: string;
2122
defaultEditorCode: string;
2223
noAutoRun?: boolean;
24+
isMask?: boolean;
2325
}) {
2426
return (
2527
<ImageDemoProvider
26-
initial={{ noAutoRun, initialCode: defaultEditorCode, name }}
28+
initial={{
29+
noAutoRun,
30+
initialCode: defaultEditorCode,
31+
name,
32+
isMask,
33+
}}
2734
>
2835
<div style={{ display: 'inline-flex', flexDirection: 'column', gap: 4 }}>
2936
<div
@@ -50,16 +57,21 @@ export default function ImageDemo({
5057
}
5158

5259
function ImageDemoImages() {
53-
const { selectedDevice, selectedImage } = useDemoStateContext();
60+
const { selectedDevice, selectedImage, isMask } = useDemoStateContext();
61+
5462
const selectImage = useSelectImage();
5563

5664
useEffect(() => {
5765
if (!selectedImage && !selectedDevice) {
5866
// This is only true when the context is first initialized
5967
// So this will run only once per demo
60-
selectImage(defaultImages[0]);
68+
if (isMask) {
69+
selectImage(defaultMasks[0]);
70+
} else {
71+
selectImage(defaultImages[0]);
72+
}
6173
}
62-
}, [selectedImage, selectedDevice, selectImage]);
74+
}, [selectedImage, selectedDevice, selectImage, isMask]);
6375
return (
6476
<div
6577
style={{

src/demo/components/addons/CodeEditorAddon.tsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import { useRunCode } from '../../contexts/demo/dispatchHelpers';
1616
import { convertCodeToFunction } from '../../utils/convertCodeToFunction';
1717

1818
export default function CodeEditorAddon(props: { defaultEditorCode: string }) {
19-
const { addon, noAutoRun, run, selectedDevice, code } = useDemoStateContext();
19+
const { addon, noAutoRun, run, selectedDevice, code, isMask } =
20+
useDemoStateContext();
2021
const demoDispatch = useDemoDispatchContext();
2122
const [editorValue, setEditorValue] = useState(props.defaultEditorCode);
2223
const debouncedEditorValue = useDebounce(editorValue, 1000);
@@ -33,14 +34,14 @@ export default function CodeEditorAddon(props: { defaultEditorCode: string }) {
3334
if (!noAutoRun && code !== debouncedEditorValue) {
3435
// Check for syntax errors before dispatching the code
3536
try {
36-
convertCodeToFunction(debouncedEditorValue);
37+
convertCodeToFunction(debouncedEditorValue, isMask);
3738
runCode(debouncedEditorValue);
3839
} catch (e) {
3940
// Ignore
4041
// The code editor should highlight the syntax error
4142
}
4243
}
43-
}, [debouncedEditorValue, runCode, noAutoRun, code]);
44+
}, [debouncedEditorValue, runCode, noAutoRun, code, isMask]);
4445

4546
// Video streams cannot be stopped, instead the code can be updated at anytime
4647
// And the new code will apply on the next frame

src/demo/components/image/ExpandableImageDuo.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ export default function ExpandableImageDuo() {
1313
const expandableImages: ImageSrc[] = [];
1414
if (sourceImage.type === 'url') {
1515
expandableImages.push(sourceImage.value);
16-
} else {
16+
} else if (sourceImage.type === 'image') {
1717
expandableImages.push(sourceImage.image);
18+
} else {
19+
expandableImages.push(sourceImage.mask);
1820
}
1921
expandableImages.push(filteredImage);
2022

src/demo/components/providers/ImportImageProvider.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
import React, { ReactNode, useMemo, useReducer } from 'react';
22

3-
import { defaultImages } from '../../contexts/demo/defaultImages';
3+
import { defaultImages, defaultMasks } from '../../contexts/demo/defaultImages';
44
import {
5-
ImageDemoInputOption,
65
imageContext,
6+
ImageDemoInputOption,
77
} from '../../contexts/importImage/importImageContext';
88

99
export function ImportImageProvider(props: { children: ReactNode }) {
10-
const [images, addImages] = useReducer(
10+
const [options, addOptions] = useReducer(
1111
(state: ImageDemoInputOption[], newOptions: ImageDemoInputOption[]) => {
1212
newOptions.forEach((newOption) => {
1313
while (state.find((option) => option.value === newOption.value)) {
@@ -25,10 +25,9 @@ export function ImportImageProvider(props: { children: ReactNode }) {
2525
}
2626
}
2727
});
28-
const newState = [...state, ...newOptions];
29-
return newState;
28+
return [...state, ...newOptions];
3029
},
31-
defaultImages,
30+
[...defaultImages, ...defaultMasks],
3231
);
3332

3433
const [isVideoStreamAllowed, allowVideoStream] = useReducer(
@@ -37,8 +36,13 @@ export function ImportImageProvider(props: { children: ReactNode }) {
3736
);
3837

3938
const contextValue = useMemo(() => {
40-
return { images, addImages, isVideoStreamAllowed, allowVideoStream };
41-
}, [images, addImages, isVideoStreamAllowed, allowVideoStream]);
39+
return {
40+
options,
41+
addOptions,
42+
isVideoStreamAllowed,
43+
allowVideoStream,
44+
};
45+
}, [options, addOptions, isVideoStreamAllowed, allowVideoStream]);
4246

4347
return (
4448
<imageContext.Provider value={contextValue}>

0 commit comments

Comments
 (0)