big update
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import { Setting } from '../store/Atoms'
|
||||
import { dataURItoBlob } from '../utils'
|
||||
|
||||
export const API_ENDPOINT = `${process.env.REACT_APP_INPAINTING_URL}/inpaint`
|
||||
export const API_ENDPOINT = `${process.env.REACT_APP_INPAINTING_URL}`
|
||||
|
||||
export default async function inpaint(
|
||||
imageFile: File,
|
||||
maskBase64: string,
|
||||
settings: Setting,
|
||||
sizeLimit?: string
|
||||
) {
|
||||
// 1080, 2000, Original
|
||||
@@ -13,13 +15,22 @@ export default async function inpaint(
|
||||
const mask = dataURItoBlob(maskBase64)
|
||||
fd.append('mask', mask)
|
||||
|
||||
fd.append('ldmSteps', settings.ldmSteps.toString())
|
||||
fd.append('hdStrategy', settings.hdStrategy)
|
||||
fd.append('hdStrategyCropMargin', settings.hdStrategyCropMargin.toString())
|
||||
fd.append(
|
||||
'hdStrategyCropTrigerSize',
|
||||
settings.hdStrategyCropTrigerSize.toString()
|
||||
)
|
||||
fd.append('hdStrategyResizeLimit', settings.hdStrategyResizeLimit.toString())
|
||||
|
||||
if (sizeLimit === undefined) {
|
||||
fd.append('sizeLimit', '1080')
|
||||
} else {
|
||||
fd.append('sizeLimit', sizeLimit)
|
||||
}
|
||||
|
||||
const res = await fetch(API_ENDPOINT, {
|
||||
const res = await fetch(`${API_ENDPOINT}/inpaint`, {
|
||||
method: 'POST',
|
||||
body: fd,
|
||||
}).then(async r => {
|
||||
@@ -28,3 +39,12 @@ export default async function inpaint(
|
||||
|
||||
return URL.createObjectURL(res)
|
||||
}
|
||||
|
||||
export function switchModel(name: string) {
|
||||
const fd = new FormData()
|
||||
fd.append('name', name)
|
||||
return fetch(`${API_ENDPOINT}/switch_model`, {
|
||||
method: 'POST',
|
||||
body: fd,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -15,12 +15,14 @@ import {
|
||||
TransformComponent,
|
||||
TransformWrapper,
|
||||
} from 'react-zoom-pan-pinch'
|
||||
import { useRecoilValue } from 'recoil'
|
||||
import { useWindowSize, useKey, useKeyPressEvent } from 'react-use'
|
||||
import inpaint from '../../adapters/inpainting'
|
||||
import Button from '../shared/Button'
|
||||
import Slider from './Slider'
|
||||
import SizeSelector from './SizeSelector'
|
||||
import { downloadImage, loadImage, useImage } from '../../utils'
|
||||
import { settingState } from '../../store/Atoms'
|
||||
|
||||
const TOOLBAR_SIZE = 200
|
||||
const BRUSH_COLOR = '#ffcc00bb'
|
||||
@@ -57,6 +59,7 @@ function drawLines(
|
||||
|
||||
export default function Editor(props: EditorProps) {
|
||||
const { file } = props
|
||||
const settings = useRecoilValue(settingState)
|
||||
const [brushSize, setBrushSize] = useState(40)
|
||||
const [original, isOriginalLoaded] = useImage(file)
|
||||
const [renders, setRenders] = useState<HTMLImageElement[]>([])
|
||||
@@ -125,6 +128,7 @@ export default function Editor(props: EditorProps) {
|
||||
const res = await inpaint(
|
||||
file,
|
||||
maskCanvas.toDataURL(),
|
||||
settings,
|
||||
sizeLimit.toString()
|
||||
)
|
||||
if (!res) {
|
||||
@@ -157,6 +161,7 @@ export default function Editor(props: EditorProps) {
|
||||
renders,
|
||||
sizeLimit,
|
||||
historyLineCount,
|
||||
settings,
|
||||
])
|
||||
|
||||
const hadDrawSomething = () => {
|
||||
|
||||
@@ -6,7 +6,7 @@ import Button from '../shared/Button'
|
||||
import Shortcuts from '../Shortcuts/Shortcuts'
|
||||
import useResolution from '../../hooks/useResolution'
|
||||
import { ThemeChanger } from './ThemeChanger'
|
||||
import SettingIcon from '../Setting/SettingIcon'
|
||||
import SettingIcon from '../Settings/SettingIcon'
|
||||
|
||||
const Header = () => {
|
||||
const [file, setFile] = useRecoilState(fileState)
|
||||
|
||||
@@ -1,50 +1,16 @@
|
||||
import React, { ReactNode } from 'react'
|
||||
import { useRecoilState } from 'recoil'
|
||||
import { settingState } from '../../store/Atoms'
|
||||
import NumberInput from '../shared/NumberInput'
|
||||
import Selector from '../shared/Selector'
|
||||
import NumberInputSetting from './NumberInputSetting'
|
||||
import SettingBlock from './SettingBlock'
|
||||
|
||||
export enum HDStrategy {
|
||||
ORIGINAL = 'Original',
|
||||
REISIZE = 'Resize',
|
||||
RESIZE = 'Resize',
|
||||
CROP = 'Crop',
|
||||
}
|
||||
|
||||
interface PixelSizeInputProps {
|
||||
title: string
|
||||
value: string
|
||||
onValue: (val: string) => void
|
||||
}
|
||||
|
||||
function PixelSizeInputSetting(props: PixelSizeInputProps) {
|
||||
const { title, value, onValue } = props
|
||||
|
||||
return (
|
||||
<SettingBlock
|
||||
className="sub-setting-block"
|
||||
title={title}
|
||||
input={
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
}}
|
||||
>
|
||||
<NumberInput
|
||||
style={{ width: '80px' }}
|
||||
value={`${value}`}
|
||||
onValue={onValue}
|
||||
/>
|
||||
<span>pixel</span>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function HDSettingBlock() {
|
||||
const [setting, setSettingState] = useRecoilState(settingState)
|
||||
|
||||
@@ -84,7 +50,7 @@ function HDSettingBlock() {
|
||||
tabIndex={0}
|
||||
role="button"
|
||||
className="inline-tip"
|
||||
onClick={() => onStrategyChange(HDStrategy.REISIZE)}
|
||||
onClick={() => onStrategyChange(HDStrategy.RESIZE)}
|
||||
>
|
||||
Resize Strategy
|
||||
</div>{' '}
|
||||
@@ -100,9 +66,10 @@ function HDSettingBlock() {
|
||||
Resize the longer side of the image to a specific size(keep ratio),
|
||||
then do inpainting on the resized image.
|
||||
</div>
|
||||
<PixelSizeInputSetting
|
||||
<NumberInputSetting
|
||||
title="Size limit"
|
||||
value={`${setting.hdStrategyResizeLimit}`}
|
||||
suffix="pixel"
|
||||
onValue={onResizeLimitChange}
|
||||
/>
|
||||
</div>
|
||||
@@ -117,14 +84,16 @@ function HDSettingBlock() {
|
||||
the result back. Mainly for performance and memory reasons on high
|
||||
resolution image.
|
||||
</div>
|
||||
<PixelSizeInputSetting
|
||||
<NumberInputSetting
|
||||
title="Trigger size"
|
||||
value={`${setting.hdStrategyCropTrigerSize}`}
|
||||
suffix="pixel"
|
||||
onValue={onCropTriggerSizeChange}
|
||||
/>
|
||||
<PixelSizeInputSetting
|
||||
<NumberInputSetting
|
||||
title="Crop margin"
|
||||
value={`${setting.hdStrategyCropMargin}`}
|
||||
suffix="pixel"
|
||||
onValue={onCropMarginChange}
|
||||
/>
|
||||
</div>
|
||||
@@ -137,7 +106,7 @@ function HDSettingBlock() {
|
||||
return renderOriginalOptionDesc()
|
||||
case HDStrategy.CROP:
|
||||
return renderCropOptionDesc()
|
||||
case HDStrategy.REISIZE:
|
||||
case HDStrategy.RESIZE:
|
||||
return renderResizeOptionDesc()
|
||||
default:
|
||||
return renderOriginalOptionDesc()
|
||||
@@ -2,11 +2,12 @@ import React, { ReactNode } from 'react'
|
||||
import { useRecoilState } from 'recoil'
|
||||
import { settingState } from '../../store/Atoms'
|
||||
import Selector from '../shared/Selector'
|
||||
import NumberInputSetting from './NumberInputSetting'
|
||||
import SettingBlock from './SettingBlock'
|
||||
|
||||
export enum AIModel {
|
||||
LAMA = 'LaMa',
|
||||
LDM = 'LDM',
|
||||
LAMA = 'lama',
|
||||
LDM = 'ldm',
|
||||
}
|
||||
|
||||
function ModelSettingBlock() {
|
||||
@@ -24,7 +25,7 @@ function ModelSettingBlock() {
|
||||
githubUrl: string
|
||||
) => {
|
||||
return (
|
||||
<div style={{ display: 'flex', flexDirection: 'column' }}>
|
||||
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
|
||||
<a
|
||||
className="model-desc-link"
|
||||
href={paperUrl}
|
||||
@@ -34,14 +35,11 @@ function ModelSettingBlock() {
|
||||
{name}
|
||||
</a>
|
||||
|
||||
<br />
|
||||
|
||||
<a
|
||||
className="model-desc-link"
|
||||
href={githubUrl}
|
||||
target="_blank"
|
||||
rel="noreferrer noopener"
|
||||
style={{ marginTop: '8px' }}
|
||||
>
|
||||
{githubUrl}
|
||||
</a>
|
||||
@@ -49,6 +47,28 @@ function ModelSettingBlock() {
|
||||
)
|
||||
}
|
||||
|
||||
const renderLDMModelDesc = () => {
|
||||
return (
|
||||
<div>
|
||||
{renderModelDesc(
|
||||
'High-Resolution Image Synthesis with Latent Diffusion Models',
|
||||
'https://arxiv.org/abs/2112.10752',
|
||||
'https://github.com/CompVis/latent-diffusion'
|
||||
)}
|
||||
<NumberInputSetting
|
||||
title="Steps"
|
||||
value={`${setting.ldmSteps}`}
|
||||
onValue={value => {
|
||||
const val = value.length === 0 ? 0 : parseInt(value, 10)
|
||||
setSettingState(old => {
|
||||
return { ...old, ldmSteps: val }
|
||||
})
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const renderOptionDesc = (): ReactNode => {
|
||||
switch (setting.model) {
|
||||
case AIModel.LAMA:
|
||||
@@ -58,11 +78,7 @@ function ModelSettingBlock() {
|
||||
'https://github.com/saic-mdal/lama'
|
||||
)
|
||||
case AIModel.LDM:
|
||||
return renderModelDesc(
|
||||
'High-Resolution Image Synthesis with Latent Diffusion Models',
|
||||
'https://arxiv.org/abs/2112.10752',
|
||||
'https://github.com/CompVis/latent-diffusion'
|
||||
)
|
||||
return renderLDMModelDesc()
|
||||
default:
|
||||
return <></>
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import React from 'react'
|
||||
import NumberInput from '../shared/NumberInput'
|
||||
import SettingBlock from './SettingBlock'
|
||||
|
||||
interface NumberInputSettingProps {
|
||||
title: string
|
||||
value: string
|
||||
suffix?: string
|
||||
onValue: (val: string) => void
|
||||
}
|
||||
|
||||
function NumberInputSetting(props: NumberInputSettingProps) {
|
||||
const { title, value, suffix, onValue } = props
|
||||
|
||||
return (
|
||||
<SettingBlock
|
||||
className="sub-setting-block"
|
||||
title={title}
|
||||
input={
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
gap: '8px',
|
||||
}}
|
||||
>
|
||||
<NumberInput
|
||||
style={{ width: '80px' }}
|
||||
value={`${value}`}
|
||||
onValue={onValue}
|
||||
/>
|
||||
{suffix && <span>{suffix}</span>}
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export default NumberInputSetting
|
||||
@@ -1,14 +1,26 @@
|
||||
import React, { ReactNode } from 'react'
|
||||
import { useRecoilState } from 'recoil'
|
||||
import { settingState } from '../../store/Atoms'
|
||||
import { Switch, SwitchThumb } from '../shared/Switch'
|
||||
import SettingBlock from './SettingBlock'
|
||||
|
||||
function SavePathSettingBlock() {
|
||||
const [setting, setSettingState] = useRecoilState(settingState)
|
||||
|
||||
const onCheckChange = (checked: boolean) => {
|
||||
setSettingState(old => {
|
||||
return { ...old, saveImageBesideOrigin: checked }
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingBlock
|
||||
title="Download image beside origin image"
|
||||
input={
|
||||
<Switch defaultChecked>
|
||||
<Switch
|
||||
checked={setting.saveImageBesideOrigin}
|
||||
onCheckedChange={onCheckChange}
|
||||
>
|
||||
<SwitchThumb />
|
||||
</Switch>
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
margin-top: 12px;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 0.3rem;
|
||||
padding: 2rem;
|
||||
padding: 1rem;
|
||||
|
||||
.sub-setting-block {
|
||||
margin-top: 8px;
|
||||
@@ -8,7 +8,6 @@
|
||||
background-color: var(--modal-bg);
|
||||
color: var(--modal-text-color);
|
||||
box-shadow: 0px 0px 20px rgb(0, 0, 40, 0.2);
|
||||
min-height: 600px;
|
||||
width: 700px;
|
||||
|
||||
@include mobile {
|
||||
@@ -1,11 +1,11 @@
|
||||
import React from 'react'
|
||||
|
||||
import { useRecoilState } from 'recoil'
|
||||
import { switchModel } from '../../adapters/inpainting'
|
||||
import { settingState } from '../../store/Atoms'
|
||||
import Modal from '../shared/Modal'
|
||||
import HDSettingBlock from './HDSettingBlock'
|
||||
import ModelSettingBlock from './ModelSettingBlock'
|
||||
import SavePathSettingBlock from './SavePathSettingBlock'
|
||||
|
||||
export default function SettingModal() {
|
||||
const [setting, setSettingState] = useRecoilState(settingState)
|
||||
@@ -14,6 +14,8 @@ export default function SettingModal() {
|
||||
setSettingState(old => {
|
||||
return { ...old, show: false }
|
||||
})
|
||||
|
||||
switchModel(setting.model)
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -23,7 +25,9 @@ export default function SettingModal() {
|
||||
className="modal-setting"
|
||||
show={setting.show}
|
||||
>
|
||||
<SavePathSettingBlock />
|
||||
{/* It's not possible because this poses a security risk */}
|
||||
{/* https://stackoverflow.com/questions/34870711/download-a-file-at-different-location-using-html5 */}
|
||||
{/* <SavePathSettingBlock /> */}
|
||||
<ModelSettingBlock />
|
||||
<HDSettingBlock />
|
||||
</Modal>
|
||||
@@ -1,9 +1,7 @@
|
||||
import React from 'react'
|
||||
import { useRecoilValue } from 'recoil'
|
||||
import Editor from './Editor/Editor'
|
||||
import { shortcutsState } from '../store/Atoms'
|
||||
import ShortcutsModal from './Shortcuts/ShortcutsModal'
|
||||
import SettingModal from './Setting/SettingModal'
|
||||
import SettingModal from './Settings/SettingsModal'
|
||||
|
||||
interface WorkspaceProps {
|
||||
file: File
|
||||
|
||||
@@ -16,11 +16,15 @@ export default function Modal(props: ModalProps) {
|
||||
const ref = useRef(null)
|
||||
|
||||
useClickAway(ref, () => {
|
||||
onClose?.()
|
||||
if (show) {
|
||||
onClose?.()
|
||||
}
|
||||
})
|
||||
|
||||
useKeyPressEvent('Escape', e => {
|
||||
onClose?.()
|
||||
if (show) {
|
||||
onClose?.()
|
||||
}
|
||||
})
|
||||
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { atom } from 'recoil'
|
||||
import { HDStrategy } from '../components/Setting/HDSettingBlock'
|
||||
import { AIModel } from '../components/Setting/ModelSettingBlock'
|
||||
import { HDStrategy } from '../components/Settings/HDSettingBlock'
|
||||
import { AIModel } from '../components/Settings/ModelSettingBlock'
|
||||
|
||||
export const fileState = atom<File | undefined>({
|
||||
key: 'fileState',
|
||||
@@ -16,10 +16,15 @@ export interface Setting {
|
||||
show: boolean
|
||||
saveImageBesideOrigin: boolean
|
||||
model: AIModel
|
||||
|
||||
// For LaMa
|
||||
hdStrategy: HDStrategy
|
||||
hdStrategyResizeLimit: number
|
||||
hdStrategyCropTrigerSize: number
|
||||
hdStrategyCropMargin: number
|
||||
|
||||
// For LDM
|
||||
ldmSteps: number
|
||||
}
|
||||
|
||||
export const settingState = atom<Setting>({
|
||||
@@ -28,9 +33,10 @@ export const settingState = atom<Setting>({
|
||||
show: false,
|
||||
saveImageBesideOrigin: false,
|
||||
model: AIModel.LAMA,
|
||||
hdStrategy: HDStrategy.ORIGINAL,
|
||||
hdStrategy: HDStrategy.RESIZE,
|
||||
hdStrategyResizeLimit: 2048,
|
||||
hdStrategyCropTrigerSize: 2048,
|
||||
hdStrategyCropMargin: 128,
|
||||
ldmSteps: 50,
|
||||
},
|
||||
})
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
@use '../components/Header/Header';
|
||||
@use '../components/Header/ThemeChanger';
|
||||
@use '../components/Shortcuts/Shortcuts';
|
||||
@use '../components/Setting/Setting.scss';
|
||||
@use '../components/Settings/Settings.scss';
|
||||
|
||||
// Shared
|
||||
@use '../components/FileSelect/FileSelect';
|
||||
|
||||
Reference in New Issue
Block a user