From f49fc960c91ac786aacdb8662856c645da07d9bc Mon Sep 17 00:00:00 2001 From: Sean Sube Date: Sun, 8 Jan 2023 22:15:58 -0600 Subject: [PATCH] feat(gui): display source images after selection --- gui/src/components/ImageInput.tsx | 54 ++++++++++++++ gui/src/components/Img2Img.tsx | 16 +--- gui/src/components/Inpaint.tsx | 120 ++++++++++++++++-------------- gui/src/config.ts | 2 + onnx-web.code-workspace | 1 + 5 files changed, 126 insertions(+), 67 deletions(-) create mode 100644 gui/src/components/ImageInput.tsx diff --git a/gui/src/components/ImageInput.tsx b/gui/src/components/ImageInput.tsx new file mode 100644 index 00000000..bc4a9567 --- /dev/null +++ b/gui/src/components/ImageInput.tsx @@ -0,0 +1,54 @@ +import { doesExist, mustDefault, mustExist } from '@apextoaster/js-utils'; +import { PhotoCamera } from '@mui/icons-material'; +import { Button, Stack } from '@mui/material'; +import * as React from 'react'; + +const { useState } = React; + +export interface ImageInputProps { + filter: string; + hidden?: boolean; + label: string; + + onChange: (file: File) => void; + renderImage?: (image: string | undefined) => React.ReactNode; +} + +export function ImageInput(props: ImageInputProps) { + const [image, setImage] = useState(); + + function renderImage() { + if (mustDefault(props.hidden, false)) { + return undefined; + } + + if (doesExist(props.renderImage)) { + return props.renderImage(image); + } + + return ; + } + + return + + {renderImage()} + ; +} diff --git a/gui/src/components/Img2Img.tsx b/gui/src/components/Img2Img.tsx index 6d8c7445..1bb1f08a 100644 --- a/gui/src/components/Img2Img.tsx +++ b/gui/src/components/Img2Img.tsx @@ -1,11 +1,12 @@ -import { doesExist, mustExist } from '@apextoaster/js-utils'; +import { mustExist } from '@apextoaster/js-utils'; import { Box, Button, Stack } from '@mui/material'; import * as React from 'react'; import { useMutation, useQuery } from 'react-query'; import { ApiClient, BaseImgParams } from '../api/client.js'; -import { Config, CONFIG_DEFAULTS, STALE_TIME } from '../config.js'; +import { Config, CONFIG_DEFAULTS, IMAGE_FILTER, STALE_TIME } from '../config.js'; import { SCHEDULER_LABELS } from '../strings.js'; +import { ImageInput } from './ImageInput.js'; import { ImageCard } from './ImageCard.js'; import { ImageControl } from './ImageControl.js'; import { MutationHistory } from './MutationHistory.js'; @@ -36,15 +37,6 @@ export function Img2Img(props: Img2ImgProps) { }); } - function changeSource(event: React.ChangeEvent) { - if (doesExist(event.target.files)) { - const file = event.target.files[0]; - if (doesExist(file)) { - setSource(file); - } - } - } - const upload = useMutation(uploadSource); const schedulers = useQuery('schedulers', async () => client.schedulers(), { staleTime: STALE_TIME, @@ -74,7 +66,7 @@ export function Img2Img(props: Img2ImgProps) { }} /> - + { setParams(newParams); }} /> diff --git a/gui/src/components/Inpaint.tsx b/gui/src/components/Inpaint.tsx index 38a17755..478dd795 100644 --- a/gui/src/components/Inpaint.tsx +++ b/gui/src/components/Inpaint.tsx @@ -5,8 +5,9 @@ import * as React from 'react'; import { useMutation, useQuery } from 'react-query'; import { ApiClient, ApiResponse, BaseImgParams, equalResponse } from '../api/client.js'; -import { Config, CONFIG_DEFAULTS, STALE_TIME } from '../config.js'; +import { Config, CONFIG_DEFAULTS, DEFAULT_BRUSH, IMAGE_FILTER, STALE_TIME } from '../config.js'; import { SCHEDULER_LABELS } from '../strings.js'; +import { ImageInput } from './ImageInput.js'; import { ImageCard } from './ImageCard.js'; import { ImageControl } from './ImageControl.js'; import { MutationHistory } from './MutationHistory.js'; @@ -15,7 +16,6 @@ import { QueryList } from './QueryList.js'; const { useEffect, useRef, useState } = React; -export const DEFAULT_BRUSH = 8; export const FULL_CIRCLE = 2 * Math.PI; export const PIXEL_SIZE = 4; export const PIXEL_WEIGHT = 3; @@ -69,14 +69,13 @@ export function Inpaint(props: InpaintProps) { async function uploadSource() { const canvas = mustExist(canvasRef.current); return new Promise((res, _rej) => { - canvas.toBlob((value) => { - const mask = mustExist(value); + canvas.toBlob((blob) => { res(client.inpaint({ ...params, model, platform, scheduler, - mask, + mask: mustExist(blob), source: mustExist(source), })); }); @@ -93,13 +92,19 @@ export function Inpaint(props: InpaintProps) { image.src = URL.createObjectURL(file); } - function changeSource(event: React.ChangeEvent) { - if (doesExist(event.target.files)) { - const file = event.target.files[0]; - if (doesExist(file)) { - setSource(file); - drawSource(file); - } + function changeMask(file: File) { + setMask(file); + + // always draw the mask to the canvas + drawSource(file); + } + + function changeSource(file: File) { + setSource(file); + + // draw the source to the canvas if the mask has not been set + if (doesExist(mask) === false) { + drawSource(file); } } @@ -156,6 +161,7 @@ export function Inpaint(props: InpaintProps) { const [brushColor, setBrushColor] = useState(0); const [brushSize, setBrushSize] = useState(DEFAULT_BRUSH); + const [mask, setMask] = useState(); const [source, setSource] = useState(); const [params, setParams] = useState({ cfg: CONFIG_DEFAULTS.cfg.default, @@ -179,6 +185,50 @@ export function Inpaint(props: InpaintProps) { clicks.length = 0; }, [clicks.length]); + function renderCanvas() { + return { + const canvas = mustExist(canvasRef.current); + const bounds = canvas.getBoundingClientRect(); + + setClicks([...clicks, { + x: event.clientX - bounds.left, + y: event.clientY - bounds.top, + }]); + }} + onMouseDown={() => { + setPainting(true); + }} + onMouseLeave={() => { + setPainting(false); + }} + onMouseOut={() => { + setPainting(false); + }} + onMouseUp={() => { + setPainting(false); + }} + onMouseMove={(event) => { + if (painting) { + const canvas = mustExist(canvasRef.current); + const bounds = canvas.getBoundingClientRect(); + + setClicks([...clicks, { + x: event.clientX - bounds.left, + y: event.clientY - bounds.top, + }]); + } + }} + />; + } + return @@ -193,48 +243,8 @@ export function Inpaint(props: InpaintProps) { }} /> - - { - const canvas = mustExist(canvasRef.current); - const bounds = canvas.getBoundingClientRect(); - - setClicks([...clicks, { - x: event.clientX - bounds.left, - y: event.clientY - bounds.top, - }]); - }} - onMouseDown={() => { - setPainting(true); - }} - onMouseLeave={() => { - setPainting(false); - }} - onMouseOut={() => { - setPainting(false); - }} - onMouseUp={() => { - setPainting(false); - }} - onMouseMove={(event) => { - if (painting) { - const canvas = mustExist(canvasRef.current); - const bounds = canvas.getBoundingClientRect(); - - setClicks([...clicks, { - x: event.clientX - bounds.left, - y: event.clientY - bounds.top, - }]); - } - }} - /> + + } onClick={() => floodMask(floodAbove)}> - Gray to white + Gray to white { diff --git a/gui/src/config.ts b/gui/src/config.ts index af511c51..e18f59a1 100644 --- a/gui/src/config.ts +++ b/gui/src/config.ts @@ -37,6 +37,8 @@ export type ConfigRanges = { [K in KeyFilter]: T[K] extends number ? ConfigRange : T[K] extends string ? string : never; }; +export const DEFAULT_BRUSH = 8; +export const IMAGE_FILTER = '.bmp, .jpg, .jpeg, .png'; export const IMAGE_STEP = 8; export const IMAGE_MAX = 512; diff --git a/onnx-web.code-workspace b/onnx-web.code-workspace index 6ec51947..2dbb0be4 100644 --- a/onnx-web.code-workspace +++ b/onnx-web.code-workspace @@ -18,6 +18,7 @@ "directml", "ftfy", "huggingface", + "Inpaint", "Multistep", "numpy", "Onnx",