add adjust mask feature
This commit is contained in:
@@ -52,9 +52,9 @@ const DiffusionProgress = () => {
|
||||
|
||||
return (
|
||||
<div
|
||||
className="fixed w-[220px] left-1/2 -translate-x-1/2 top-[68px] h-[32px] flex justify-center items-center gap-[18px] border-[1px] border-[solid] rounded-[14px] pl-[8px] pr-[8px]"
|
||||
className="z-10 fixed bg-background w-[220px] left-1/2 -translate-x-1/2 top-[68px] h-[32px] flex justify-center items-center gap-[18px] border-[1px] border-[solid] rounded-[14px] pl-[8px] pr-[8px]"
|
||||
style={{
|
||||
visibility: isInpainting && isConnected && isSD ? "visible" : "hidden",
|
||||
visibility: isConnected && isInpainting && isSD ? "visible" : "hidden",
|
||||
}}
|
||||
>
|
||||
<Progress value={progress} />
|
||||
|
||||
@@ -95,6 +95,7 @@ export default function Editor(props: EditorProps) {
|
||||
const brushSize = useStore((state) => state.getBrushSize())
|
||||
const renders = useStore((state) => state.editorState.renders)
|
||||
const extraMasks = useStore((state) => state.editorState.extraMasks)
|
||||
const temporaryMasks = useStore((state) => state.editorState.temporaryMasks)
|
||||
const lineGroups = useStore((state) => state.editorState.lineGroups)
|
||||
const curLineGroup = useStore((state) => state.editorState.curLineGroup)
|
||||
|
||||
@@ -166,6 +167,9 @@ export default function Editor(props: EditorProps) {
|
||||
context.canvas.width = imageWidth
|
||||
context.canvas.height = imageHeight
|
||||
context.clearRect(0, 0, context.canvas.width, context.canvas.height)
|
||||
temporaryMasks.forEach((maskImage) => {
|
||||
context.drawImage(maskImage, 0, 0, imageWidth, imageHeight)
|
||||
})
|
||||
extraMasks.forEach((maskImage) => {
|
||||
context.drawImage(maskImage, 0, 0, imageWidth, imageHeight)
|
||||
})
|
||||
@@ -182,20 +186,9 @@ export default function Editor(props: EditorProps) {
|
||||
imageHeight
|
||||
)
|
||||
}
|
||||
if (
|
||||
!interactiveSegState.isInteractiveSeg &&
|
||||
interactiveSegState.interactiveSegMask
|
||||
) {
|
||||
context.drawImage(
|
||||
interactiveSegState.interactiveSegMask,
|
||||
0,
|
||||
0,
|
||||
imageWidth,
|
||||
imageHeight
|
||||
)
|
||||
}
|
||||
drawLines(context, curLineGroup)
|
||||
}, [
|
||||
temporaryMasks,
|
||||
extraMasks,
|
||||
isOriginalLoaded,
|
||||
interactiveSegState,
|
||||
|
||||
@@ -21,7 +21,6 @@ const PromptInput = () => {
|
||||
state.hidePrevMask,
|
||||
])
|
||||
const ref = useRef(null)
|
||||
|
||||
useClickAway<MouseEvent>(ref, () => {
|
||||
if (ref?.current) {
|
||||
const input = ref.current as HTMLInputElement
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { FormEvent } from "react"
|
||||
import { FormEvent, useRef } from "react"
|
||||
import { useStore } from "@/lib/states"
|
||||
import { Switch } from "../ui/switch"
|
||||
import { NumberInput } from "../ui/input"
|
||||
@@ -18,7 +18,8 @@ import { Slider } from "../ui/slider"
|
||||
import { useImage } from "@/hooks/useImage"
|
||||
import { INSTRUCT_PIX2PIX, PAINT_BY_EXAMPLE, POWERPAINT } from "@/lib/const"
|
||||
import { RowContainer, LabelTitle } from "./LabelTitle"
|
||||
import { Upload } from "lucide-react"
|
||||
import { Minus, Plus, Upload } from "lucide-react"
|
||||
import { useClickAway } from "react-use"
|
||||
|
||||
const ExtenderButton = ({
|
||||
text,
|
||||
@@ -31,7 +32,7 @@ const ExtenderButton = ({
|
||||
return (
|
||||
<Button
|
||||
variant="outline"
|
||||
className="p-1 h-7"
|
||||
className="p-1 h-8"
|
||||
disabled={!showExtender}
|
||||
onClick={onClick}
|
||||
>
|
||||
@@ -51,6 +52,8 @@ const DiffusionOptions = () => {
|
||||
updateAppState,
|
||||
updateExtenderByBuiltIn,
|
||||
updateExtenderDirection,
|
||||
adjustMask,
|
||||
clearMask,
|
||||
] = useStore((state) => [
|
||||
state.serverConfig.samplers,
|
||||
state.settings,
|
||||
@@ -61,8 +64,17 @@ const DiffusionOptions = () => {
|
||||
state.updateAppState,
|
||||
state.updateExtenderByBuiltIn,
|
||||
state.updateExtenderDirection,
|
||||
state.adjustMask,
|
||||
state.clearMask,
|
||||
])
|
||||
const [exampleImage, isExampleImageLoaded] = useImage(paintByExampleFile)
|
||||
const negativePromptRef = useRef(null)
|
||||
useClickAway<MouseEvent>(negativePromptRef, () => {
|
||||
if (negativePromptRef?.current) {
|
||||
const input = negativePromptRef.current as HTMLInputElement
|
||||
input.blur()
|
||||
}
|
||||
})
|
||||
|
||||
const onKeyUp = (e: React.KeyboardEvent) => {
|
||||
// negativePrompt 回车触发 inpainting
|
||||
@@ -320,6 +332,7 @@ const DiffusionOptions = () => {
|
||||
/>
|
||||
<div className="pl-2 pr-4">
|
||||
<Textarea
|
||||
ref={negativePromptRef}
|
||||
rows={4}
|
||||
onKeyUp={onKeyUp}
|
||||
className="max-h-[8rem] overflow-y-auto mb-2"
|
||||
@@ -550,7 +563,7 @@ const DiffusionOptions = () => {
|
||||
<RowContainer>
|
||||
<LabelTitle
|
||||
text="Task"
|
||||
toolTip="When using extender, image-outpainting task will be auto used. For object-removal and image-outpainting, it is recommended to set the guidance_scale at 10 or above."
|
||||
toolTip="PowerPaint task. When using extender, image-outpainting task will be auto used. For object-removal and image-outpainting, it is recommended to set the guidance_scale at 10 or above."
|
||||
/>
|
||||
<Select
|
||||
defaultValue={settings.powerpaintTask}
|
||||
@@ -760,10 +773,85 @@ const DiffusionOptions = () => {
|
||||
)
|
||||
}
|
||||
|
||||
const renderMaskAdjuster = () => {
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col gap-1">
|
||||
<LabelTitle
|
||||
htmlFor="adjustMaskKernelSize"
|
||||
text="Adjust Mask"
|
||||
toolTip="Expand or shrink mask. Using the slider to adjust the kernel size for dilation or erosion."
|
||||
/>
|
||||
<RowContainer>
|
||||
<Slider
|
||||
className="w-[180px]"
|
||||
defaultValue={[12]}
|
||||
min={1}
|
||||
max={100}
|
||||
step={1}
|
||||
value={[Math.floor(settings.adjustMaskKernelSize)]}
|
||||
onValueChange={(vals) =>
|
||||
updateSettings({ adjustMaskKernelSize: vals[0] })
|
||||
}
|
||||
/>
|
||||
<NumberInput
|
||||
id="adjustMaskKernelSize"
|
||||
className="w-[60px] rounded-full"
|
||||
numberValue={settings.adjustMaskKernelSize}
|
||||
allowFloat={false}
|
||||
onNumberValueChange={(val) => {
|
||||
updateSettings({ adjustMaskKernelSize: val })
|
||||
}}
|
||||
/>
|
||||
</RowContainer>
|
||||
|
||||
<RowContainer>
|
||||
<div className="flex gap-1 justify-start">
|
||||
<Button
|
||||
variant="outline"
|
||||
className="p-1 h-8"
|
||||
onClick={() => adjustMask("expand")}
|
||||
disabled={isProcessing}
|
||||
>
|
||||
<div className="flex items-center gap-1 select-none">
|
||||
<Plus size={16} />
|
||||
expand
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
className="p-1 h-8"
|
||||
onClick={() => adjustMask("shrink")}
|
||||
disabled={isProcessing}
|
||||
>
|
||||
<div className="flex items-center gap-1 select-none">
|
||||
<Minus size={16} />
|
||||
Shrink
|
||||
</div>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
<Button
|
||||
variant="outline"
|
||||
className="p-1 h-8 justify-self-end"
|
||||
onClick={clearMask}
|
||||
disabled={isProcessing}
|
||||
>
|
||||
<div className="flex items-center gap-1 select-none">Clear</div>
|
||||
</Button>
|
||||
</RowContainer>
|
||||
</div>
|
||||
<Separator />
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex flex-col gap-4 mt-4">
|
||||
{renderCropper()}
|
||||
{renderExtender()}
|
||||
{renderMaskAdjuster()}
|
||||
{renderPowerPaintTaskType()}
|
||||
{renderSteps()}
|
||||
{renderGuidanceScale()}
|
||||
|
||||
202
web_app/src/components/ui/context-menu.tsx
Normal file
202
web_app/src/components/ui/context-menu.tsx
Normal file
@@ -0,0 +1,202 @@
|
||||
import * as React from "react"
|
||||
import * as ContextMenuPrimitive from "@radix-ui/react-context-menu"
|
||||
import {
|
||||
CheckIcon,
|
||||
ChevronRightIcon,
|
||||
DotFilledIcon,
|
||||
} from "@radix-ui/react-icons"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
const ContextMenu = ContextMenuPrimitive.Root
|
||||
|
||||
const ContextMenuTrigger = ContextMenuPrimitive.Trigger
|
||||
|
||||
const ContextMenuGroup = ContextMenuPrimitive.Group
|
||||
|
||||
const ContextMenuPortal = ContextMenuPrimitive.Portal
|
||||
|
||||
const ContextMenuSub = ContextMenuPrimitive.Sub
|
||||
|
||||
const ContextMenuRadioGroup = ContextMenuPrimitive.RadioGroup
|
||||
|
||||
const ContextMenuSubTrigger = React.forwardRef<
|
||||
React.ElementRef<typeof ContextMenuPrimitive.SubTrigger>,
|
||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubTrigger> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, children, ...props }, ref) => (
|
||||
<ContextMenuPrimitive.SubTrigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
{children}
|
||||
<ChevronRightIcon className="ml-auto h-4 w-4" />
|
||||
</ContextMenuPrimitive.SubTrigger>
|
||||
))
|
||||
ContextMenuSubTrigger.displayName = ContextMenuPrimitive.SubTrigger.displayName
|
||||
|
||||
const ContextMenuSubContent = React.forwardRef<
|
||||
React.ElementRef<typeof ContextMenuPrimitive.SubContent>,
|
||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.SubContent>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ContextMenuPrimitive.SubContent
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
ContextMenuSubContent.displayName = ContextMenuPrimitive.SubContent.displayName
|
||||
|
||||
const ContextMenuContent = React.forwardRef<
|
||||
React.ElementRef<typeof ContextMenuPrimitive.Content>,
|
||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Content>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ContextMenuPrimitive.Portal>
|
||||
<ContextMenuPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
</ContextMenuPrimitive.Portal>
|
||||
))
|
||||
ContextMenuContent.displayName = ContextMenuPrimitive.Content.displayName
|
||||
|
||||
const ContextMenuItem = React.forwardRef<
|
||||
React.ElementRef<typeof ContextMenuPrimitive.Item>,
|
||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Item> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<ContextMenuPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
ContextMenuItem.displayName = ContextMenuPrimitive.Item.displayName
|
||||
|
||||
const ContextMenuCheckboxItem = React.forwardRef<
|
||||
React.ElementRef<typeof ContextMenuPrimitive.CheckboxItem>,
|
||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.CheckboxItem>
|
||||
>(({ className, children, checked, ...props }, ref) => (
|
||||
<ContextMenuPrimitive.CheckboxItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
checked={checked}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<ContextMenuPrimitive.ItemIndicator>
|
||||
<CheckIcon className="h-4 w-4" />
|
||||
</ContextMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</ContextMenuPrimitive.CheckboxItem>
|
||||
))
|
||||
ContextMenuCheckboxItem.displayName =
|
||||
ContextMenuPrimitive.CheckboxItem.displayName
|
||||
|
||||
const ContextMenuRadioItem = React.forwardRef<
|
||||
React.ElementRef<typeof ContextMenuPrimitive.RadioItem>,
|
||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.RadioItem>
|
||||
>(({ className, children, ...props }, ref) => (
|
||||
<ContextMenuPrimitive.RadioItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||
<ContextMenuPrimitive.ItemIndicator>
|
||||
<DotFilledIcon className="h-4 w-4 fill-current" />
|
||||
</ContextMenuPrimitive.ItemIndicator>
|
||||
</span>
|
||||
{children}
|
||||
</ContextMenuPrimitive.RadioItem>
|
||||
))
|
||||
ContextMenuRadioItem.displayName = ContextMenuPrimitive.RadioItem.displayName
|
||||
|
||||
const ContextMenuLabel = React.forwardRef<
|
||||
React.ElementRef<typeof ContextMenuPrimitive.Label>,
|
||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Label> & {
|
||||
inset?: boolean
|
||||
}
|
||||
>(({ className, inset, ...props }, ref) => (
|
||||
<ContextMenuPrimitive.Label
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"px-2 py-1.5 text-sm font-semibold text-foreground",
|
||||
inset && "pl-8",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
ContextMenuLabel.displayName = ContextMenuPrimitive.Label.displayName
|
||||
|
||||
const ContextMenuSeparator = React.forwardRef<
|
||||
React.ElementRef<typeof ContextMenuPrimitive.Separator>,
|
||||
React.ComponentPropsWithoutRef<typeof ContextMenuPrimitive.Separator>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<ContextMenuPrimitive.Separator
|
||||
ref={ref}
|
||||
className={cn("-mx-1 my-1 h-px bg-border", className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
ContextMenuSeparator.displayName = ContextMenuPrimitive.Separator.displayName
|
||||
|
||||
const ContextMenuShortcut = ({
|
||||
className,
|
||||
...props
|
||||
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||
return (
|
||||
<span
|
||||
className={cn(
|
||||
"ml-auto text-xs tracking-widest text-muted-foreground",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
ContextMenuShortcut.displayName = "ContextMenuShortcut"
|
||||
|
||||
export {
|
||||
ContextMenu,
|
||||
ContextMenuTrigger,
|
||||
ContextMenuContent,
|
||||
ContextMenuItem,
|
||||
ContextMenuCheckboxItem,
|
||||
ContextMenuRadioItem,
|
||||
ContextMenuLabel,
|
||||
ContextMenuSeparator,
|
||||
ContextMenuShortcut,
|
||||
ContextMenuGroup,
|
||||
ContextMenuPortal,
|
||||
ContextMenuSub,
|
||||
ContextMenuSubContent,
|
||||
ContextMenuSubTrigger,
|
||||
ContextMenuRadioGroup,
|
||||
}
|
||||
@@ -17,6 +17,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
||||
const handleOnBlur = () => {
|
||||
updateAppState({ disableShortCuts: false })
|
||||
}
|
||||
|
||||
return (
|
||||
<input
|
||||
type={type}
|
||||
|
||||
Reference in New Issue
Block a user