This commit is contained in:
Qing 2023-12-16 13:34:56 +08:00
parent cbe6577890
commit 24e95daac1
16 changed files with 381 additions and 323 deletions

View File

@ -101,7 +101,6 @@ if __name__ == "__main__":
model = ModelManager(
name=args.name,
device=device,
sd_run_local=True,
disable_nsfw=True,
sd_cpu_textencoder=True,
hf_access_token="123"

View File

@ -65,7 +65,7 @@ Run Stable Diffusion text encoder model on CPU to save GPU memory.
SD_CONTROLNET_HELP = """
Run Stable Diffusion normal or inpainting model with ControlNet.
"""
DEFAULT_SD_CONTROLNET_METHOD = "thibaud/controlnet-sd21-openpose-diffusers"
DEFAULT_SD_CONTROLNET_METHOD = "lllyasviel/control_v11p_sd15_canny"
SD_CONTROLNET_CHOICES = [
"lllyasviel/control_v11p_sd15_canny",
# "lllyasviel/control_v11p_sd15_seg",

View File

@ -66,9 +66,9 @@ class InstructPix2Pix(DiffusionInpaintModel):
image=PIL.Image.fromarray(image),
prompt=config.prompt,
negative_prompt=config.negative_prompt,
num_inference_steps=config.p2p_steps,
num_inference_steps=config.sd_steps,
image_guidance_scale=config.p2p_image_guidance_scale,
guidance_scale=config.p2p_guidance_scale,
guidance_scale=config.sd_guidance_scale,
output_type="np",
generator=torch.manual_seed(config.sd_seed),
).images[0]

View File

@ -19,7 +19,7 @@ class PaintByExample(DiffusionInpaintModel):
fp16 = not kwargs.get("no_half", False)
use_gpu = device == torch.device("cuda") and torch.cuda.is_available()
torch_dtype = torch.float16 if use_gpu and fp16 else torch.float32
model_kwargs = {"local_files_only": kwargs.get("local_files_only", False)}
model_kwargs = {}
if kwargs["disable_nsfw"] or kwargs.get("cpu_offload", False):
logger.info("Disable Paint By Example Model NSFW checker")
@ -58,24 +58,17 @@ class PaintByExample(DiffusionInpaintModel):
image=PIL.Image.fromarray(image),
mask_image=PIL.Image.fromarray(mask[:, :, -1], mode="L"),
example_image=config.paint_by_example_example_image,
num_inference_steps=config.paint_by_example_steps,
num_inference_steps=config.sd_steps,
guidance_scale=config.sd_guidance_scale,
negative_prompt="out of frame, lowres, error, cropped, worst quality, low quality, jpeg artifacts, ugly, duplicate, morbid, mutilated, out of frame, mutation, deformed, blurry, dehydrated, bad anatomy, bad proportions, extra limbs, disfigured, gross proportions, malformed limbs, watermark, signature",
output_type="np.array",
generator=torch.manual_seed(config.paint_by_example_seed),
generator=torch.manual_seed(config.sd_seed),
).images[0]
output = (output * 255).round().astype("uint8")
output = cv2.cvtColor(output, cv2.COLOR_RGB2BGR)
return output
def forward_post_process(self, result, image, mask, config):
if config.paint_by_example_match_histograms:
result = self._match_histograms(result, image[:, :, ::-1], mask)
if config.paint_by_example_mask_blur != 0:
k = 2 * config.paint_by_example_mask_blur + 1
mask = cv2.GaussianBlur(mask, (k, k), 0)
return result, image, mask
@staticmethod
def is_downloaded() -> bool:
# model will be downloaded when app start, and can't switch in frontend settings

View File

@ -1,3 +1,4 @@
import gc
import math
import random
from typing import Any
@ -913,6 +914,7 @@ def torch_gc():
if torch.cuda.is_available():
torch.cuda.empty_cache()
torch.cuda.ipc_collect()
gc.collect()
def set_seed(seed: int):

View File

View File

@ -2,9 +2,8 @@
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + React + TS</title>
<title>Lama Cleaner</title>
</head>
<body>
<div id="root"></div>

View File

@ -50,22 +50,12 @@ export default function Editor(props: EditorProps) {
imageHeight,
settings,
enableAutoSaving,
cropperRect,
enableManualInpainting,
setImageSize,
setBaseBrushSize,
setIsInpainting,
setSeed,
interactiveSegState,
updateInteractiveSegState,
resetInteractiveSegState,
isPluginRunning,
setIsPluginRunning,
handleCanvasMouseDown,
handleCanvasMouseMove,
cleanCurLineGroup,
updateEditorState,
resetRedoState,
undo,
redo,
undoDisabled,
@ -82,22 +72,12 @@ export default function Editor(props: EditorProps) {
state.imageHeight,
state.settings,
state.serverConfig.enableAutoSaving,
state.cropperState,
state.settings.enableManualInpainting,
state.setImageSize,
state.setBaseBrushSize,
state.setIsInpainting,
state.setSeed,
state.interactiveSegState,
state.updateInteractiveSegState,
state.resetInteractiveSegState,
state.isPluginRunning,
state.setIsPluginRunning,
state.handleCanvasMouseDown,
state.handleCanvasMouseMove,
state.cleanCurLineGroup,
state.updateEditorState,
state.resetRedoState,
state.undo,
state.redo,
state.undoDisabled(),
@ -112,9 +92,7 @@ export default function Editor(props: EditorProps) {
const renders = useStore((state) => state.editorState.renders)
const extraMasks = useStore((state) => state.editorState.extraMasks)
const lineGroups = useStore((state) => state.editorState.lineGroups)
const lastLineGroup = useStore((state) => state.editorState.lastLineGroup)
const curLineGroup = useStore((state) => state.editorState.curLineGroup)
const redoLineGroups = useStore((state) => state.editorState.redoLineGroups)
// Local State
const [showOriginal, setShowOriginal] = useState(false)
@ -338,8 +316,6 @@ export default function Editor(props: EditorProps) {
if (isDraging) {
setIsDraging(false)
// setCurLineGroup([])
// drawOnCurrentRender([])
} else {
resetZoom()
}
@ -434,22 +410,6 @@ export default function Editor(props: EditorProps) {
}
}
const isOutsideCroper = (clickPnt: { x: number; y: number }) => {
if (clickPnt.x < cropperRect.x) {
return true
}
if (clickPnt.y < cropperRect.y) {
return true
}
if (clickPnt.x > cropperRect.x + cropperRect.width) {
return true
}
if (clickPnt.y > cropperRect.y + cropperRect.height) {
return true
}
return false
}
const onCanvasMouseUp = (ev: SyntheticEvent) => {
if (interactiveSegState.isInteractiveSeg) {
const xy = mouseXY(ev)
@ -491,15 +451,6 @@ export default function Editor(props: EditorProps) {
return
}
// if (
// isDiffusionModels &&
// settings.showCroper &&
// isOutsideCroper(mouseXY(ev))
// ) {
// // TODO: 去掉这个逻辑,在 cropper 层截断 click 点击?
// return
// }
setIsDraging(true)
handleCanvasMouseDown(mouseXY(ev))
}
@ -850,15 +801,6 @@ export default function Editor(props: EditorProps) {
)
}
// const onInteractiveAccept = () => {
// setInteractiveSegMask(tmpInteractiveSegMask)
// setTmpInteractiveSegMask(null)
// if (!enableManualInpainting && tmpInteractiveSegMask) {
// runInpainting(false, undefined, tmpInteractiveSegMask)
// }
// }
return (
<div
className="flex w-screen h-screen justify-center items-center"

View File

@ -1,5 +1,5 @@
import { FormEvent, useState } from "react"
import { useToggle, useWindowSize } from "react-use"
import { FormEvent } from "react"
import { useToggle } from "react-use"
import { useStore } from "@/lib/states"
import { Switch } from "./ui/switch"
import { Label } from "./ui/label"
@ -16,39 +16,45 @@ import { Textarea } from "./ui/textarea"
import { SDSampler } from "@/lib/types"
import { Separator } from "./ui/separator"
import { ScrollArea } from "./ui/scroll-area"
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "./ui/sheet"
import { ChevronLeft, ChevronRight } from "lucide-react"
import { Button } from "./ui/button"
import { Sheet, SheetContent, SheetHeader, SheetTrigger } from "./ui/sheet"
import { ChevronLeft, ChevronRight, Upload } from "lucide-react"
import { Button, ImageUploadButton } from "./ui/button"
import useHotKey from "@/hooks/useHotkey"
import { Slider } from "./ui/slider"
import { useImage } from "@/hooks/useImage"
import { INSTRUCT_PIX2PIX, PAINT_BY_EXAMPLE } from "@/lib/const"
const RowContainer = ({ children }: { children: React.ReactNode }) => (
<div className="flex justify-between items-center pr-2">{children}</div>
)
const SidePanel = () => {
const [settings, updateSettings, showSidePanel, runInpainting] = useStore(
(state) => [
state.settings,
state.updateSettings,
state.showSidePanel(),
state.runInpainting,
]
)
const [
settings,
windowSize,
paintByExampleFile,
isProcessing,
updateSettings,
showSidePanel,
runInpainting,
updateAppState,
] = useStore((state) => [
state.settings,
state.windowSize,
state.paintByExampleFile,
state.getIsProcessing(),
state.updateSettings,
state.showSidePanel(),
state.runInpainting,
state.updateAppState,
])
const [exampleImage, isExampleImageLoaded] = useImage(paintByExampleFile)
const [open, toggleOpen] = useToggle(false)
useHotKey("c", () => {
toggleOpen()
})
const windowSize = useWindowSize()
if (!showSidePanel) {
return null
}
@ -72,20 +78,47 @@ const SidePanel = () => {
<Label htmlFor="controlnet">Controlnet</Label>
<Switch
id="controlnet"
checked={settings.enableControlNet}
checked={settings.enableControlnet}
onCheckedChange={(value) => {
updateSettings({ enableControlNet: value })
updateSettings({ enableControlnet: value })
}}
/>
</div>
<div className="pl-1 pr-2">
<div className="flex flex-col gap-1">
<RowContainer>
<Slider
className="w-[180px]"
defaultValue={[100]}
min={1}
max={100}
step={1}
disabled={!settings.enableControlnet}
value={[Math.floor(settings.controlnetConditioningScale * 100)]}
onValueChange={(vals) =>
updateSettings({ controlnetConditioningScale: vals[0] / 100 })
}
/>
<NumberInput
id="controlnet-weight"
className="w-[60px] rounded-full"
disabled={!settings.enableControlnet}
numberValue={settings.controlnetConditioningScale}
allowFloat={false}
onNumberValueChange={(val) => {
updateSettings({ controlnetConditioningScale: val })
}}
/>
</RowContainer>
</div>
<div className="pr-2">
<Select
value={settings.controlnetMethod}
onValueChange={(value) => {
updateSettings({ controlnetMethod: value })
}}
disabled={!settings.enableControlNet}
disabled={!settings.enableControlnet}
>
<SelectTrigger>
<SelectValue placeholder="Select control method" />
@ -102,26 +135,6 @@ const SidePanel = () => {
</Select>
</div>
</div>
<RowContainer>
<Label
htmlFor="controlnet-weight"
disabled={!settings.enableControlNet}
>
weight
</Label>
<NumberInput
id="controlnet-weight"
className="w-14"
disabled={!settings.enableControlNet}
numberValue={settings.controlnetConditioningScale}
allowFloat
onNumberValueChange={(value) => {
updateSettings({ controlnetConditioningScale: value })
}}
/>
</RowContainer>
<Separator />
</div>
)
@ -166,74 +179,79 @@ const SidePanel = () => {
}}
/>
</div>
<div className="flex gap-3">
<div className="flex flex-col gap-2 items-start">
<Label htmlFor="freeu-s1" disabled={!settings.enableFreeu}>
s1
</Label>
<NumberInput
id="freeu-s1"
className="w-14"
disabled={!settings.enableFreeu}
numberValue={settings.freeuConfig.s1}
allowFloat
onNumberValueChange={(value) => {
updateSettings({
freeuConfig: { ...settings.freeuConfig, s1: value },
})
}}
/>
<div className="flex flex-col gap-4">
<div className="flex justify-center gap-6">
<div className="flex gap-2 items-center justify-center">
<Label htmlFor="freeu-s1" disabled={!settings.enableFreeu}>
s1
</Label>
<NumberInput
id="freeu-s1"
className="w-14"
disabled={!settings.enableFreeu}
numberValue={settings.freeuConfig.s1}
allowFloat
onNumberValueChange={(value) => {
updateSettings({
freeuConfig: { ...settings.freeuConfig, s1: value },
})
}}
/>
</div>
<div className="flex gap-2 items-center justify-center">
<Label htmlFor="freeu-s2" disabled={!settings.enableFreeu}>
s2
</Label>
<NumberInput
id="freeu-s2"
className="w-14"
disabled={!settings.enableFreeu}
numberValue={settings.freeuConfig.s2}
allowFloat
onNumberValueChange={(value) => {
updateSettings({
freeuConfig: { ...settings.freeuConfig, s2: value },
})
}}
/>
</div>
</div>
<div className="flex flex-col gap-2 items-start">
<Label htmlFor="freeu-s2" disabled={!settings.enableFreeu}>
s2
</Label>
<NumberInput
id="freeu-s2"
className="w-14"
disabled={!settings.enableFreeu}
numberValue={settings.freeuConfig.s2}
allowFloat
onNumberValueChange={(value) => {
updateSettings({
freeuConfig: { ...settings.freeuConfig, s2: value },
})
}}
/>
</div>
<div className="flex flex-col gap-2 items-start">
<Label htmlFor="freeu-b1" disabled={!settings.enableFreeu}>
b1
</Label>
<NumberInput
id="freeu-b1"
className="w-14"
disabled={!settings.enableFreeu}
numberValue={settings.freeuConfig.b1}
allowFloat
onNumberValueChange={(value) => {
updateSettings({
freeuConfig: { ...settings.freeuConfig, b1: value },
})
}}
/>
</div>
<div className="flex flex-col gap-2 items-start">
<Label htmlFor="freeu-b2" disabled={!settings.enableFreeu}>
b2
</Label>
<NumberInput
id="freeu-b2"
className="w-14"
disabled={!settings.enableFreeu}
numberValue={settings.freeuConfig.b2}
allowFloat
onNumberValueChange={(value) => {
updateSettings({
freeuConfig: { ...settings.freeuConfig, b2: value },
})
}}
/>
<div className="flex justify-center gap-6">
<div className="flex gap-2 items-center justify-center">
<Label htmlFor="freeu-b1" disabled={!settings.enableFreeu}>
b1
</Label>
<NumberInput
id="freeu-b1"
className="w-14"
disabled={!settings.enableFreeu}
numberValue={settings.freeuConfig.b1}
allowFloat
onNumberValueChange={(value) => {
updateSettings({
freeuConfig: { ...settings.freeuConfig, b1: value },
})
}}
/>
</div>
<div className="flex gap-2 items-center justify-center">
<Label htmlFor="freeu-b2" disabled={!settings.enableFreeu}>
b2
</Label>
<NumberInput
id="freeu-b2"
className="w-14"
disabled={!settings.enableFreeu}
numberValue={settings.freeuConfig.b2}
allowFloat
onNumberValueChange={(value) => {
updateSettings({
freeuConfig: { ...settings.freeuConfig, b2: value },
})
}}
/>
</div>
</div>
</div>
<Separator />
@ -241,6 +259,110 @@ const SidePanel = () => {
)
}
const renderNegativePrompt = () => {
if (!settings.model.need_prompt) {
return null
}
return (
<div className="flex flex-col gap-4">
<Label htmlFor="negative-prompt">Negative prompt</Label>
<div className="pl-2 pr-4">
<Textarea
rows={4}
onKeyUp={onKeyUp}
className="max-h-[8rem] overflow-y-auto mb-2"
placeholder=""
id="negative-prompt"
value={settings.negativePrompt}
onInput={(evt: FormEvent<HTMLTextAreaElement>) => {
evt.preventDefault()
evt.stopPropagation()
const target = evt.target as HTMLTextAreaElement
updateSettings({ negativePrompt: target.value })
}}
/>
</div>
</div>
)
}
const renderPaintByExample = () => {
if (settings.model.name !== PAINT_BY_EXAMPLE) {
return null
}
return (
<div>
<RowContainer>
<div>Example Image</div>
<ImageUploadButton
tooltip="Upload example image"
onFileUpload={(file) => {
updateAppState({ paintByExampleFile: file })
}}
>
<Upload />
</ImageUploadButton>
</RowContainer>
{isExampleImageLoaded ? (
<div className="flex justify-center items-center">
<img
src={exampleImage.src}
alt="example"
className="max-w-[200px] max-h-[200px] m-3"
/>
</div>
) : (
<></>
)}
<Button
variant="default"
className="w-full"
disabled={isProcessing || !isExampleImageLoaded}
onClick={() => {
runInpainting()
}}
>
Paint
</Button>
</div>
)
}
const renderP2PImageGuidanceScale = () => {
if (settings.model.name !== INSTRUCT_PIX2PIX) {
return null
}
return (
<div className="flex flex-col gap-1">
<Label htmlFor="image-guidance-scale">Image guidance scale</Label>
<RowContainer>
<Slider
className="w-[180px]"
defaultValue={[150]}
min={100}
max={1000}
step={1}
value={[Math.floor(settings.p2pImageGuidanceScale * 100)]}
onValueChange={(vals) =>
updateSettings({ p2pImageGuidanceScale: vals[0] / 100 })
}
/>
<NumberInput
id="image-guidance-scale"
className="w-[60px] rounded-full"
numberValue={settings.p2pImageGuidanceScale}
allowFloat
onNumberValueChange={(val) => {
updateSettings({ p2pImageGuidanceScale: val })
}}
/>
</RowContainer>
</div>
)
}
return (
<Sheet open={open} modal={false}>
<SheetTrigger
@ -263,7 +385,7 @@ const SidePanel = () => {
onOpenAutoFocus={(event) => event.preventDefault()}
onPointerDownOutside={(event) => event.preventDefault()}
>
<SheetHeader className="mb-4">
<SheetHeader>
<RowContainer>
<div className="overflow-hidden mr-8">
{
@ -287,7 +409,7 @@ const SidePanel = () => {
style={{ height: windowSize.height - 160 }}
className="pr-3"
>
<div className="flex flex-col gap-3">
<div className="flex flex-col gap-4 mt-4">
<RowContainer>
<Label htmlFor="cropper">Cropper</Label>
<Switch
@ -299,50 +421,83 @@ const SidePanel = () => {
/>
</RowContainer>
<RowContainer>
<div className="flex flex-col gap-1">
<Label htmlFor="steps">Steps</Label>
<NumberInput
id="steps"
className="w-14"
numberValue={settings.sdSteps}
allowFloat={false}
onNumberValueChange={(value) => {
updateSettings({ sdSteps: value })
}}
/>
</RowContainer>
<RowContainer>
<Slider
className="w-[180px]"
defaultValue={[30]}
min={1}
max={100}
step={1}
value={[Math.floor(settings.sdSteps)]}
onValueChange={(vals) => updateSettings({ sdSteps: vals[0] })}
/>
<NumberInput
id="steps"
className="w-[60px] rounded-full"
numberValue={settings.sdSteps}
allowFloat={false}
onNumberValueChange={(val) => {
updateSettings({ sdSteps: val })
}}
/>
</RowContainer>
</div>
<RowContainer>
<div className="flex flex-col gap-1">
<Label htmlFor="guidance-scale">Guidance scale</Label>
<NumberInput
id="guidance-scale"
className="w-14"
numberValue={settings.sdGuidanceScale}
allowFloat
onNumberValueChange={(value) => {
updateSettings({ sdGuidanceScale: value })
}}
/>
</RowContainer>
<RowContainer>
<Slider
className="w-[180px]"
defaultValue={[750]}
min={100}
max={1500}
step={1}
value={[Math.floor(settings.sdGuidanceScale * 100)]}
onValueChange={(vals) =>
updateSettings({ sdGuidanceScale: vals[0] / 100 })
}
/>
<NumberInput
id="guidance-scale"
className="w-[60px] rounded-full"
numberValue={settings.sdGuidanceScale}
allowFloat
onNumberValueChange={(val) => {
updateSettings({ sdGuidanceScale: val })
}}
/>
</RowContainer>
</div>
<RowContainer>
<div className="flex gap-2 items-center">
<Label htmlFor="strength">Strength</Label>
<div className="text-sm">({settings.sdStrength})</div>
</div>
<Slider
className="w-24"
defaultValue={[100]}
min={10}
max={100}
step={1}
tabIndex={-1}
value={[Math.floor(settings.sdStrength * 100)]}
onValueChange={(vals) =>
updateSettings({ sdStrength: vals[0] / 100 })
}
/>
</RowContainer>
{renderP2PImageGuidanceScale()}
<div className="flex flex-col gap-1">
<Label htmlFor="strength">Strength</Label>
<RowContainer>
<Slider
className="w-[180px]"
defaultValue={[100]}
min={10}
max={100}
step={1}
value={[Math.floor(settings.sdStrength * 100)]}
onValueChange={(vals) =>
updateSettings({ sdStrength: vals[0] / 100 })
}
/>
<NumberInput
id="strength"
className="w-[60px] rounded-full"
numberValue={settings.sdStrength}
allowFloat
onNumberValueChange={(val) => {
updateSettings({ sdStrength: val })
}}
/>
</RowContainer>
</div>
<RowContainer>
<Label htmlFor="sampler">Sampler</Label>
@ -383,7 +538,7 @@ const SidePanel = () => {
}}
/>
<NumberInput
title="Seed"
id="seed"
className="w-[100px]"
disabled={!settings.seedFixed}
numberValue={settings.seed}
@ -395,46 +550,39 @@ const SidePanel = () => {
</div>
</RowContainer>
<div className="flex flex-col gap-4">
<Label htmlFor="negative-prompt">Negative prompt</Label>
<div className="pl-2 pr-4">
<Textarea
rows={4}
onKeyUp={onKeyUp}
className="max-h-[8rem] overflow-y-auto mb-2"
placeholder=""
id="negative-prompt"
value={settings.negativePrompt}
onInput={(evt: FormEvent<HTMLTextAreaElement>) => {
evt.preventDefault()
evt.stopPropagation()
const target = evt.target as HTMLTextAreaElement
updateSettings({ negativePrompt: target.value })
}}
/>
</div>
</div>
{renderNegativePrompt()}
<Separator />
<div className="flex flex-col gap-4">
{renderConterNetSetting()}
</div>
{renderConterNetSetting()}
{renderFreeu()}
{renderLCMLora()}
<RowContainer>
<div className="flex flex-col gap-1">
<Label htmlFor="mask-blur">Mask blur</Label>
<NumberInput
id="mask-blur"
className="w-14"
numberValue={settings.sdMaskBlur}
allowFloat={false}
onNumberValueChange={(value) => {
updateSettings({ sdMaskBlur: value })
}}
/>
</RowContainer>
<RowContainer>
<Slider
className="w-[180px]"
defaultValue={[5]}
min={0}
max={35}
step={1}
value={[Math.floor(settings.sdMaskBlur)]}
onValueChange={(vals) =>
updateSettings({ sdMaskBlur: vals[0] })
}
/>
<NumberInput
id="mask-blur"
className="w-[60px] rounded-full"
numberValue={settings.sdMaskBlur}
allowFloat={false}
onNumberValueChange={(value) => {
updateSettings({ sdMaskBlur: value })
}}
/>
</RowContainer>
</div>
<RowContainer>
<Label htmlFor="match-histograms">Match histograms</Label>
@ -446,6 +594,10 @@ const SidePanel = () => {
}}
/>
</RowContainer>
<Separator />
{renderPaintByExample()}
</div>
</ScrollArea>
</SheetContent>

View File

@ -25,6 +25,7 @@ const SelectTrigger = React.forwardRef<
"flex h-9 w-full items-center justify-between whitespace-nowrap rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-1 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
className
)}
tabIndex={-1}
{...props}
>
{children}
@ -84,6 +85,7 @@ const SelectContent = React.forwardRef<
className
)}
position={position}
onCloseAutoFocus={(event) => event.preventDefault()}
{...props}
>
<SelectScrollUpButton />

View File

@ -13,14 +13,15 @@ const Slider = React.forwardRef<
"relative flex w-full touch-none select-none items-center",
className
)}
tabIndex={-1}
{...props}
>
<SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20">
<SliderPrimitive.Range className="absolute h-full bg-primary" />
<SliderPrimitive.Track className="relative h-1.5 w-full grow overflow-hidden rounded-full bg-primary/20 data-[disabled]:cursor-not-allowed">
<SliderPrimitive.Range className="absolute h-full bg-primary data-[disabled]:cursor-not-allowed " />
</SliderPrimitive.Track>
<SliderPrimitive.Thumb
tabIndex={-1}
className="block h-4 w-4 rounded-full border border-primary/60 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50"
className="block h-4 w-4 rounded-full border border-primary/60 bg-background shadow transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring data-[disabled]:cursor-not-allowed"
/>
</SliderPrimitive.Root>
))

View File

@ -12,6 +12,7 @@ const Switch = React.forwardRef<
"peer inline-flex h-5 w-9 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent shadow-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
className
)}
tabIndex={-1}
{...props}
ref={ref}
>

View File

@ -11,7 +11,8 @@ const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
<textarea
className={cn(
"flex min-h-[60px] w-full rounded-md border border-input bg-transparent px-3 py-2 text-sm shadow-sm placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-50",
className
className,
"overflow-auto"
)}
tabIndex={-1}
ref={ref}

View File

@ -51,20 +51,6 @@ export default async function inpaint(
fd.append("cv2Radius", settings.cv2Radius.toString())
fd.append("cv2Flag", settings.cv2Flag.toString())
fd.append("paintByExampleSteps", settings.paintByExampleSteps.toString())
fd.append(
"paintByExampleGuidanceScale",
settings.paintByExampleGuidanceScale.toString()
)
fd.append("paintByExampleSeed", settings.seed.toString())
fd.append(
"paintByExampleMaskBlur",
settings.paintByExampleMaskBlur.toString()
)
fd.append(
"paintByExampleMatchHistograms",
settings.paintByExampleMatchHistograms ? "true" : "false"
)
// TODO: resize image's shortest_edge to 224 before pass to backend, save network time?
// https://huggingface.co/docs/transformers/model_doc/clip#transformers.CLIPImageProcessor
if (paintByExampleImage) {
@ -72,9 +58,7 @@ export default async function inpaint(
}
// InstructPix2Pix
fd.append("p2pSteps", settings.p2pSteps.toString())
fd.append("p2pImageGuidanceScale", settings.p2pImageGuidanceScale.toString())
fd.append("p2pGuidanceScale", settings.p2pGuidanceScale.toString())
// ControlNet
fd.append(

View File

@ -11,3 +11,5 @@ export const BRUSH_COLOR = "#ffcc00bb"
export const PAINT_BY_EXAMPLE = "Fantasy-Studio/Paint-by-Example"
export const INSTRUCT_PIX2PIX = "timbrooks/instruct-pix2pix"
export const KANDINSKY_2_2 = "kandinsky-community/kandinsky-2-2-decoder-inpaint"
export const DEFAULT_NEGATIVE_PROMPT =
"out of frame, lowres, error, cropped, worst quality, low quality, jpeg artifacts, ugly, duplicate, morbid, mutilated, out of frame, mutation, deformed, blurry, dehydrated, bad anatomy, bad proportions, extra limbs, disfigured, gross proportions, malformed limbs, watermark, signature"

View File

@ -2,7 +2,6 @@ import { persist } from "zustand/middleware"
import { shallow } from "zustand/shallow"
import { immer } from "zustand/middleware/immer"
import { castDraft } from "immer"
import { nanoid } from "nanoid"
import { createWithEqualityFn } from "zustand/traditional"
import {
CV2Flag,
@ -20,14 +19,13 @@ import {
} from "./types"
import {
DEFAULT_BRUSH_SIZE,
INSTRUCT_PIX2PIX,
DEFAULT_NEGATIVE_PROMPT,
MODEL_TYPE_INPAINT,
MODEL_TYPE_OTHER,
PAINT_BY_EXAMPLE,
} from "./const"
import { dataURItoBlob, generateMask, loadImage, srcToFile } from "./utils"
import inpaint, { runPlugin } from "./api"
import { toast, useToast } from "@/components/ui/use-toast"
import { toast } from "@/components/ui/use-toast"
type FileManagerState = {
sortBy: SortBy
@ -78,19 +76,11 @@ export type Settings = {
sdMatchHistograms: boolean
sdScale: number
// Paint by Example
paintByExampleSteps: number
paintByExampleGuidanceScale: number
paintByExampleMaskBlur: number
paintByExampleMatchHistograms: boolean
// InstructPix2Pix
p2pSteps: number
// Pix2Pix
p2pImageGuidanceScale: number
p2pGuidanceScale: number
// ControlNet
enableControlNet: boolean
enableControlnet: boolean
controlnetConditioningScale: number
controlnetMethod: string
@ -103,6 +93,8 @@ type ServerConfig = {
plugins: string[]
enableFileManager: boolean
enableAutoSaving: boolean
enableControlnet: boolean
controlnetMethod: string
}
type InteractiveSegState = {
@ -117,7 +109,6 @@ type EditorState = {
baseBrushSize: number
brushSizeScale: number
renders: HTMLImageElement[]
paintByExampleImage: File | null
lineGroups: LineGroup[]
lastLineGroup: LineGroup
curLineGroup: LineGroup
@ -130,6 +121,7 @@ type EditorState = {
type AppState = {
file: File | null
paintByExampleFile: File | null
customMask: File | null
imageHeight: number
imageWidth: number
@ -195,6 +187,7 @@ type AppAction = {
const defaultValues: AppState = {
file: null,
paintByExampleFile: null,
customMask: null,
imageHeight: 0,
imageWidth: 0,
@ -210,7 +203,6 @@ const defaultValues: AppState = {
baseBrushSize: DEFAULT_BRUSH_SIZE,
brushSizeScale: 1,
renders: [],
paintByExampleImage: null,
extraMasks: [],
lineGroups: [],
lastLineGroup: [],
@ -246,6 +238,8 @@ const defaultValues: AppState = {
plugins: [],
enableFileManager: false,
enableAutoSaving: false,
enableControlnet: false,
controlnetMethod: "lllyasviel/control_v11p_sd15_canny",
},
settings: {
model: {
@ -259,7 +253,7 @@ const defaultValues: AppState = {
is_single_file_diffusers: false,
need_prompt: false,
},
enableControlNet: false,
enableControlnet: false,
showCroper: false,
enableDownloadMask: false,
enableManualInpainting: false,
@ -270,7 +264,7 @@ const defaultValues: AppState = {
cv2Radius: 5,
cv2Flag: CV2Flag.INPAINT_NS,
prompt: "",
negativePrompt: "",
negativePrompt: DEFAULT_NEGATIVE_PROMPT,
seed: 42,
seedFixed: false,
sdMaskBlur: 5,
@ -280,13 +274,7 @@ const defaultValues: AppState = {
sdSampler: SDSampler.uni_pc,
sdMatchHistograms: false,
sdScale: 100,
paintByExampleSteps: 50,
paintByExampleGuidanceScale: 7.5,
paintByExampleMaskBlur: 5,
paintByExampleMatchHistograms: false,
p2pSteps: 50,
p2pImageGuidanceScale: 1.5,
p2pGuidanceScale: 7.5,
controlnetConditioningScale: 0.4,
controlnetMethod: "lllyasviel/control_v11p_sd15_canny",
enableLCMLora: false,
@ -320,6 +308,7 @@ export const useStore = createWithEqualityFn<AppState & AppAction>()(
const {
isInpainting,
file,
paintByExampleFile,
imageWidth,
imageHeight,
settings,
@ -332,13 +321,8 @@ export const useStore = createWithEqualityFn<AppState & AppAction>()(
if (file === null) {
return
}
const {
lastLineGroup,
curLineGroup,
lineGroups,
renders,
paintByExampleImage,
} = get().editorState
const { lastLineGroup, curLineGroup, lineGroups, renders } =
get().editorState
const { interactiveSegMask, prevInteractiveSegMask } =
get().interactiveSegState
@ -413,7 +397,7 @@ export const useStore = createWithEqualityFn<AppState & AppAction>()(
settings,
cropperState,
dataURItoBlob(maskCanvas.toDataURL()),
paintByExampleImage
paintByExampleFile
)
if (!res) {
@ -687,6 +671,8 @@ export const useStore = createWithEqualityFn<AppState & AppAction>()(
setServerConfig: (newValue: ServerConfig) => {
set((state) => {
state.serverConfig = newValue
state.settings.enableControlnet = newValue.enableControlnet
state.settings.controlnetMethod = newValue.controlnetMethod
})
},
@ -804,7 +790,7 @@ export const useStore = createWithEqualityFn<AppState & AppAction>()(
})),
{
name: "ZUSTAND_STATE", // name of the item in the storage (must be unique)
version: 0,
version: 1,
partialize: (state) =>
Object.fromEntries(
Object.entries(state).filter(([key]) =>
@ -815,9 +801,3 @@ export const useStore = createWithEqualityFn<AppState & AppAction>()(
),
shallow
)
// export const useStore = <U>(selector: (state: AppState & AppAction) => U) => {
// return createWithEqualityFn(selector, shallow)
// }
// export const useStore = createWithEqualityFn(useBaseStore, shallow)