big update

This commit is contained in:
Sanster
2022-04-16 00:11:51 +08:00
parent 2b031603ed
commit 205286a414
40 changed files with 539 additions and 376 deletions

View File

@@ -0,0 +1,7 @@
.hd-setting-block {
.inline-tip {
display: inline;
cursor: pointer;
color: var(--text-color);
}
}

View File

@@ -0,0 +1,132 @@
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 HDStrategy {
ORIGINAL = 'Original',
RESIZE = 'Resize',
CROP = 'Crop',
}
function HDSettingBlock() {
const [setting, setSettingState] = useRecoilState(settingState)
const onStrategyChange = (value: HDStrategy) => {
setSettingState(old => {
return { ...old, hdStrategy: value }
})
}
const onResizeLimitChange = (value: string) => {
const val = value.length === 0 ? 0 : parseInt(value, 10)
setSettingState(old => {
return { ...old, hdStrategyResizeLimit: val }
})
}
const onCropTriggerSizeChange = (value: string) => {
const val = value.length === 0 ? 0 : parseInt(value, 10)
setSettingState(old => {
return { ...old, hdStrategyCropTrigerSize: val }
})
}
const onCropMarginChange = (value: string) => {
const val = value.length === 0 ? 0 : parseInt(value, 10)
setSettingState(old => {
return { ...old, hdStrategyCropMargin: val }
})
}
const renderOriginalOptionDesc = () => {
return (
<div>
Use the original resolution of the picture, suitable for picture size
below 2K. Try{' '}
<div
tabIndex={0}
role="button"
className="inline-tip"
onClick={() => onStrategyChange(HDStrategy.RESIZE)}
>
Resize Strategy
</div>{' '}
if you do not get good results on high resolution images.
</div>
)
}
const renderResizeOptionDesc = () => {
return (
<div>
<div>
Resize the longer side of the image to a specific size(keep ratio),
then do inpainting on the resized image.
</div>
<NumberInputSetting
title="Size limit"
value={`${setting.hdStrategyResizeLimit}`}
suffix="pixel"
onValue={onResizeLimitChange}
/>
</div>
)
}
const renderCropOptionDesc = () => {
return (
<div>
<div>
Crop masking area from the original image to do inpainting, and paste
the result back. Mainly for performance and memory reasons on high
resolution image.
</div>
<NumberInputSetting
title="Trigger size"
value={`${setting.hdStrategyCropTrigerSize}`}
suffix="pixel"
onValue={onCropTriggerSizeChange}
/>
<NumberInputSetting
title="Crop margin"
value={`${setting.hdStrategyCropMargin}`}
suffix="pixel"
onValue={onCropMarginChange}
/>
</div>
)
}
const renderHDStrategyOptionDesc = (): ReactNode => {
switch (setting.hdStrategy) {
case HDStrategy.ORIGINAL:
return renderOriginalOptionDesc()
case HDStrategy.CROP:
return renderCropOptionDesc()
case HDStrategy.RESIZE:
return renderResizeOptionDesc()
default:
return renderOriginalOptionDesc()
}
}
return (
<SettingBlock
className="hd-setting-block"
title="High Resolution Strategy"
input={
<Selector
value={setting.hdStrategy as string}
options={Object.values(HDStrategy)}
onChange={val => onStrategyChange(val as HDStrategy)}
/>
}
optionDesc={renderHDStrategyOptionDesc()}
/>
)
}
export default HDSettingBlock

View File

@@ -0,0 +1,4 @@
.model-desc-link {
color: var(--text-color-gray);
text-decoration: none;
}

View File

@@ -0,0 +1,103 @@
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',
}
function ModelSettingBlock() {
const [setting, setSettingState] = useRecoilState(settingState)
const onModelChange = (value: AIModel) => {
setSettingState(old => {
return { ...old, model: value }
})
}
const renderModelDesc = (
name: string,
paperUrl: string,
githubUrl: string
) => {
return (
<div style={{ display: 'flex', flexDirection: 'column', gap: '4px' }}>
<a
className="model-desc-link"
href={paperUrl}
target="_blank"
rel="noreferrer noopener"
>
{name}
</a>
<a
className="model-desc-link"
href={githubUrl}
target="_blank"
rel="noreferrer noopener"
>
{githubUrl}
</a>
</div>
)
}
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:
return renderModelDesc(
'Resolution-robust Large Mask Inpainting with Fourier Convolutions',
'https://arxiv.org/abs/2109.07161',
'https://github.com/saic-mdal/lama'
)
case AIModel.LDM:
return renderLDMModelDesc()
default:
return <></>
}
}
return (
<SettingBlock
className="model-setting-block"
title="Inpainting Model"
input={
<Selector
value={setting.model as string}
options={Object.values(AIModel)}
onChange={val => onModelChange(val as AIModel)}
/>
}
optionDesc={renderOptionDesc()}
/>
)
}
export default ModelSettingBlock

View File

@@ -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

View File

@@ -0,0 +1,31 @@
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
checked={setting.saveImageBesideOrigin}
onCheckedChange={onCheckChange}
>
<SwitchThumb />
</Switch>
}
/>
)
}
export default SavePathSettingBlock

View File

@@ -0,0 +1,36 @@
.setting-block {
display: flex;
flex-direction: column;
.option-desc {
color: var(--text-color-gray);
margin-top: 12px;
border: 1px solid var(--border-color);
border-radius: 0.3rem;
padding: 1rem;
.sub-setting-block {
margin-top: 8px;
color: var(--text-color);
}
}
}
.setting-block-content {
display: flex;
justify-content: space-between;
align-items: center;
gap: 12rem;
}
.setting-block-content-title {
display: flex;
flex-direction: column;
justify-content: space-between;
}
.setting-block-desc {
font-size: 1rem;
margin-top: 8px;
color: var(--text-color-gray);
}

View File

@@ -0,0 +1,27 @@
import React, { ReactNode } from 'react'
interface SettingBlockProps {
title: string
desc?: string
input: ReactNode
optionDesc?: ReactNode
className?: string
}
function SettingBlock(props: SettingBlockProps) {
const { title, desc, input, optionDesc, className } = props
return (
<div className={`setting-block ${className}`}>
<div className="setting-block-content">
<div className="setting-block-content-title">
<span>{title}</span>
{desc && <span className="setting-block-desc">{desc}</span>}
</div>
{input}
</div>
{optionDesc && <div className="option-desc">{optionDesc}</div>}
</div>
)
}
export default SettingBlock

View File

@@ -0,0 +1,46 @@
import React from 'react'
import { useRecoilState } from 'recoil'
import { settingState } from '../../store/Atoms'
import Button from '../shared/Button'
const SettingIcon = () => {
const [setting, setSettingState] = useRecoilState(settingState)
const onClick = () => {
setSettingState({ ...setting, show: !setting.show })
}
return (
<div>
<Button
onClick={onClick}
style={{ border: 0 }}
icon={
<svg
xmlns="http://www.w3.org/2000/svg"
fill="none"
role="img"
width="28"
height="28"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth="2"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z"
/>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M15 12a3 3 0 11-6 0 3 3 0 016 0z"
/>
</svg>
}
/>
</div>
)
}
export default SettingIcon

View File

@@ -0,0 +1,20 @@
@use '../../styles/Mixins/' as *;
@import './SettingBlock.scss';
@import './HDSettingBlock.scss';
@import './ModelSettingBlock.scss';
.modal-setting {
grid-area: main-content;
background-color: var(--modal-bg);
color: var(--modal-text-color);
box-shadow: 0px 0px 20px rgb(0, 0, 40, 0.2);
width: 700px;
@include mobile {
display: grid;
width: 100%;
height: auto;
margin-top: -11rem;
animation: slideDown 0.2s ease-out;
}
}

View File

@@ -0,0 +1,35 @@
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'
export default function SettingModal() {
const [setting, setSettingState] = useRecoilState(settingState)
const onClose = () => {
setSettingState(old => {
return { ...old, show: false }
})
switchModel(setting.model)
}
return (
<Modal
onClose={onClose}
title="Settings"
className="modal-setting"
show={setting.show}
>
{/* 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>
)
}