add generate gif button
This commit is contained in:
parent
d4ec1208ae
commit
780517b91a
@ -1,5 +1,5 @@
|
|||||||
import { Rect, Settings } from '../store/Atoms'
|
import { Rect, Settings } from '../store/Atoms'
|
||||||
import { dataURItoBlob, srcToFile } from '../utils'
|
import { dataURItoBlob, loadImage, srcToFile } from '../utils'
|
||||||
|
|
||||||
export const API_ENDPOINT = `${process.env.REACT_APP_INPAINTING_URL}`
|
export const API_ENDPOINT = `${process.env.REACT_APP_INPAINTING_URL}`
|
||||||
|
|
||||||
@ -223,3 +223,34 @@ export async function downloadToOutput(
|
|||||||
throw new Error(`Something went wrong: ${error}`)
|
throw new Error(`Something went wrong: ${error}`)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function makeGif(
|
||||||
|
originFile: File,
|
||||||
|
cleanImage: HTMLImageElement,
|
||||||
|
filename: string,
|
||||||
|
mimeType: string
|
||||||
|
) {
|
||||||
|
const cleanFile = await srcToFile(cleanImage.src, filename, mimeType)
|
||||||
|
const fd = new FormData()
|
||||||
|
fd.append('origin_img', originFile)
|
||||||
|
fd.append('clean_img', cleanFile)
|
||||||
|
fd.append('filename', filename)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API_ENDPOINT}/make_gif`, {
|
||||||
|
method: 'POST',
|
||||||
|
body: fd,
|
||||||
|
})
|
||||||
|
if (!res.ok) {
|
||||||
|
const errMsg = await res.text()
|
||||||
|
throw new Error(errMsg)
|
||||||
|
}
|
||||||
|
|
||||||
|
const blob = await res.blob()
|
||||||
|
const newImage = new Image()
|
||||||
|
await loadImage(newImage, URL.createObjectURL(blob))
|
||||||
|
return newImage
|
||||||
|
} catch (error) {
|
||||||
|
throw new Error(`Something went wrong: ${error}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -70,7 +70,6 @@ const CoffeeIcon = () => {
|
|||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Sure
|
Sure
|
||||||
<Coffee />
|
|
||||||
</div>
|
</div>
|
||||||
</Button>
|
</Button>
|
||||||
</a>
|
</a>
|
||||||
|
@ -95,10 +95,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.editor-toolkit-btns {
|
.editor-toolkit-btns {
|
||||||
grid-area: toolkit-btns;
|
display: flex;
|
||||||
display: grid;
|
gap: 12px;
|
||||||
grid-auto-flow: column;
|
|
||||||
column-gap: 1rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.brush-shape {
|
.brush-shape {
|
||||||
|
@ -16,10 +16,11 @@ import {
|
|||||||
TransformComponent,
|
TransformComponent,
|
||||||
TransformWrapper,
|
TransformWrapper,
|
||||||
} from 'react-zoom-pan-pinch'
|
} from 'react-zoom-pan-pinch'
|
||||||
import { useRecoilState, useRecoilValue } from 'recoil'
|
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
|
||||||
import { useWindowSize, useKey, useKeyPressEvent } from 'react-use'
|
import { useWindowSize, useKey, useKeyPressEvent } from 'react-use'
|
||||||
import inpaint, {
|
import inpaint, {
|
||||||
downloadToOutput,
|
downloadToOutput,
|
||||||
|
makeGif,
|
||||||
postInteractiveSeg,
|
postInteractiveSeg,
|
||||||
} from '../../adapters/inpainting'
|
} from '../../adapters/inpainting'
|
||||||
import Button from '../shared/Button'
|
import Button from '../shared/Button'
|
||||||
@ -40,6 +41,7 @@ import {
|
|||||||
croperState,
|
croperState,
|
||||||
enableFileManagerState,
|
enableFileManagerState,
|
||||||
fileState,
|
fileState,
|
||||||
|
gifImageState,
|
||||||
imageHeightState,
|
imageHeightState,
|
||||||
imageWidthState,
|
imageWidthState,
|
||||||
interactiveSegClicksState,
|
interactiveSegClicksState,
|
||||||
@ -66,6 +68,7 @@ import FileSelect from '../FileSelect/FileSelect'
|
|||||||
import InteractiveSeg from '../InteractiveSeg/InteractiveSeg'
|
import InteractiveSeg from '../InteractiveSeg/InteractiveSeg'
|
||||||
import InteractiveSegConfirmActions from '../InteractiveSeg/ConfirmActions'
|
import InteractiveSegConfirmActions from '../InteractiveSeg/ConfirmActions'
|
||||||
import InteractiveSegReplaceModal from '../InteractiveSeg/ReplaceModal'
|
import InteractiveSegReplaceModal from '../InteractiveSeg/ReplaceModal'
|
||||||
|
import MakeGIF from './MakeGIF'
|
||||||
|
|
||||||
const TOOLBAR_SIZE = 200
|
const TOOLBAR_SIZE = 200
|
||||||
const MIN_BRUSH_SIZE = 10
|
const MIN_BRUSH_SIZE = 10
|
||||||
@ -112,7 +115,7 @@ export default function Editor() {
|
|||||||
const settings = useRecoilValue(settingState)
|
const settings = useRecoilValue(settingState)
|
||||||
const [seedVal, setSeed] = useRecoilState(seedState)
|
const [seedVal, setSeed] = useRecoilState(seedState)
|
||||||
const croperRect = useRecoilValue(croperState)
|
const croperRect = useRecoilValue(croperState)
|
||||||
const [toastVal, setToastState] = useRecoilState(toastState)
|
const setToastState = useSetRecoilState(toastState)
|
||||||
const [isInpainting, setIsInpainting] = useRecoilState(isInpaintingState)
|
const [isInpainting, setIsInpainting] = useRecoilState(isInpaintingState)
|
||||||
const runMannually = useRecoilValue(runManuallyState)
|
const runMannually = useRecoilValue(runManuallyState)
|
||||||
const isSD = useRecoilValue(isSDState)
|
const isSD = useRecoilValue(isSDState)
|
||||||
@ -181,8 +184,8 @@ export default function Editor() {
|
|||||||
const [redoLineGroups, setRedoLineGroups] = useState<LineGroup[]>([])
|
const [redoLineGroups, setRedoLineGroups] = useState<LineGroup[]>([])
|
||||||
const enableFileManager = useRecoilValue(enableFileManagerState)
|
const enableFileManager = useRecoilValue(enableFileManagerState)
|
||||||
|
|
||||||
const [imageWidth, setImageWidth] = useRecoilState(imageWidthState)
|
const setImageWidth = useSetRecoilState(imageWidthState)
|
||||||
const [imageHeight, setImageHeight] = useRecoilState(imageHeightState)
|
const setImageHeight = useSetRecoilState(imageHeightState)
|
||||||
const app = useRecoilValue(appState)
|
const app = useRecoilValue(appState)
|
||||||
|
|
||||||
const draw = useCallback(
|
const draw = useCallback(
|
||||||
@ -1534,6 +1537,7 @@ export default function Editor() {
|
|||||||
}}
|
}}
|
||||||
disabled={renders.length === 0}
|
disabled={renders.length === 0}
|
||||||
/>
|
/>
|
||||||
|
<MakeGIF renders={renders} />
|
||||||
<Button
|
<Button
|
||||||
toolTip="Save Image"
|
toolTip="Save Image"
|
||||||
icon={<ArrowDownTrayIcon />}
|
icon={<ArrowDownTrayIcon />}
|
||||||
|
114
lama_cleaner/app/src/components/Editor/MakeGIF.tsx
Normal file
114
lama_cleaner/app/src/components/Editor/MakeGIF.tsx
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
import React, { useState } from 'react'
|
||||||
|
import { GifIcon } from '@heroicons/react/24/outline'
|
||||||
|
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
|
||||||
|
import Button from '../shared/Button'
|
||||||
|
import { fileState, gifImageState, toastState } from '../../store/Atoms'
|
||||||
|
import { makeGif } from '../../adapters/inpainting'
|
||||||
|
import Modal from '../shared/Modal'
|
||||||
|
import { LoadingIcon } from '../shared/Toast'
|
||||||
|
import { downloadImage } from '../../utils'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
renders: HTMLImageElement[]
|
||||||
|
}
|
||||||
|
|
||||||
|
const MakeGIF = (props: Props) => {
|
||||||
|
const { renders } = props
|
||||||
|
const [gifImg, setGifImg] = useRecoilState(gifImageState)
|
||||||
|
const file = useRecoilValue(fileState)
|
||||||
|
const setToastState = useSetRecoilState(toastState)
|
||||||
|
const [show, setShow] = useState(false)
|
||||||
|
|
||||||
|
const handleOnClose = () => {
|
||||||
|
setShow(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleDownload = () => {
|
||||||
|
if (gifImg) {
|
||||||
|
const name = file.name.replace(/\.[^/.]+$/, '.gif')
|
||||||
|
downloadImage(gifImg.src, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Button
|
||||||
|
toolTip="Make Gif"
|
||||||
|
icon={<GifIcon />}
|
||||||
|
disabled={!renders.length}
|
||||||
|
onClick={async () => {
|
||||||
|
setShow(true)
|
||||||
|
setGifImg(null)
|
||||||
|
try {
|
||||||
|
const gif = await makeGif(
|
||||||
|
file,
|
||||||
|
renders[renders.length - 1],
|
||||||
|
file.name,
|
||||||
|
file.type
|
||||||
|
)
|
||||||
|
if (gif) {
|
||||||
|
setGifImg(gif)
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
setToastState({
|
||||||
|
open: true,
|
||||||
|
desc: e.message ? e.message : e.toString(),
|
||||||
|
state: 'error',
|
||||||
|
duration: 2000,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<Modal
|
||||||
|
onClose={handleOnClose}
|
||||||
|
title="GIF"
|
||||||
|
className="modal-setting"
|
||||||
|
show={show}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
flexDirection: 'column',
|
||||||
|
gap: 16,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{gifImg ? (
|
||||||
|
<img src={gifImg.src} style={{ borderRadius: 8 }} alt="gif" />
|
||||||
|
) : (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
justifyContent: 'center',
|
||||||
|
gap: 8,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<LoadingIcon />
|
||||||
|
Generating GIF...
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{gifImg && (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
width: '100%',
|
||||||
|
justifyContent: 'flex-end',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: '12px',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button onClick={handleDownload} border>
|
||||||
|
Download
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Modal>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default MakeGIF
|
@ -1,5 +1,6 @@
|
|||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useRecoilState } from 'recoil'
|
import { useRecoilState } from 'recoil'
|
||||||
|
import { Cog6ToothIcon } from '@heroicons/react/24/outline'
|
||||||
import { settingState } from '../../store/Atoms'
|
import { settingState } from '../../store/Atoms'
|
||||||
import Button from '../shared/Button'
|
import Button from '../shared/Button'
|
||||||
|
|
||||||
@ -16,29 +17,7 @@ const SettingIcon = () => {
|
|||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
toolTip="Settings"
|
toolTip="Settings"
|
||||||
style={{ border: 0 }}
|
style={{ border: 0 }}
|
||||||
icon={
|
icon={<Cog6ToothIcon />}
|
||||||
<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>
|
</div>
|
||||||
)
|
)
|
||||||
|
@ -3,7 +3,7 @@ import * as ToastPrimitive from '@radix-ui/react-toast'
|
|||||||
import { ToastProps } from '@radix-ui/react-toast'
|
import { ToastProps } from '@radix-ui/react-toast'
|
||||||
import { CheckIcon, ExclamationCircleIcon } from '@heroicons/react/24/outline'
|
import { CheckIcon, ExclamationCircleIcon } from '@heroicons/react/24/outline'
|
||||||
|
|
||||||
const LoadingIcon = () => {
|
export const LoadingIcon = () => {
|
||||||
return (
|
return (
|
||||||
<span className="loading-icon">
|
<span className="loading-icon">
|
||||||
<svg
|
<svg
|
||||||
|
@ -45,6 +45,7 @@ interface AppState {
|
|||||||
interactiveSegClicks: number[][]
|
interactiveSegClicks: number[][]
|
||||||
showFileManager: boolean
|
showFileManager: boolean
|
||||||
enableFileManager: boolean
|
enableFileManager: boolean
|
||||||
|
gifImage: HTMLImageElement | undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export const appState = atom<AppState>({
|
export const appState = atom<AppState>({
|
||||||
@ -61,6 +62,7 @@ export const appState = atom<AppState>({
|
|||||||
interactiveSegClicks: [],
|
interactiveSegClicks: [],
|
||||||
showFileManager: false,
|
showFileManager: false,
|
||||||
enableFileManager: false,
|
enableFileManager: false,
|
||||||
|
gifImage: undefined,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -134,6 +136,18 @@ export const enableFileManagerState = selector({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const gifImageState = selector({
|
||||||
|
key: 'gifImageState',
|
||||||
|
get: ({ get }) => {
|
||||||
|
const app = get(appState)
|
||||||
|
return app.gifImage
|
||||||
|
},
|
||||||
|
set: ({ get, set }, newValue: any) => {
|
||||||
|
const app = get(appState)
|
||||||
|
set(appState, { ...app, gifImage: newValue })
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
export const fileState = selector({
|
export const fileState = selector({
|
||||||
key: 'fileState',
|
key: 'fileState',
|
||||||
get: ({ get }) => {
|
get: ({ get }) => {
|
||||||
|
@ -69,6 +69,8 @@ def make_compare_gif(
|
|||||||
cubic_bezier_points = cubic_bezier((0.33, 0), (0.66, 1), 1, num_frames)
|
cubic_bezier_points = cubic_bezier((0.33, 0), (0.66, 1), 1, num_frames)
|
||||||
cubic_bezier_points.reverse()
|
cubic_bezier_points.reverse()
|
||||||
|
|
||||||
|
max_side_length = min(max_side_length, max(clean_img.size))
|
||||||
|
|
||||||
src_img = keep_ratio_resize(src_img, max_side_length)
|
src_img = keep_ratio_resize(src_img, max_side_length)
|
||||||
clean_img = keep_ratio_resize(clean_img, max_side_length)
|
clean_img = keep_ratio_resize(clean_img, max_side_length)
|
||||||
width, height = src_img.size
|
width, height = src_img.size
|
||||||
|
@ -19,6 +19,7 @@ from loguru import logger
|
|||||||
from watchdog.events import FileSystemEventHandler
|
from watchdog.events import FileSystemEventHandler
|
||||||
|
|
||||||
from lama_cleaner.interactive_seg import InteractiveSeg, Click
|
from lama_cleaner.interactive_seg import InteractiveSeg, Click
|
||||||
|
from lama_cleaner.make_gif import make_compare_gif
|
||||||
from lama_cleaner.model_manager import ModelManager
|
from lama_cleaner.model_manager import ModelManager
|
||||||
from lama_cleaner.schema import Config
|
from lama_cleaner.schema import Config
|
||||||
from lama_cleaner.file_manager import FileManager
|
from lama_cleaner.file_manager import FileManager
|
||||||
@ -93,6 +94,26 @@ def diffuser_callback(i, t, latents):
|
|||||||
# socketio.emit('diffusion_step', {'diffusion_step': step})
|
# socketio.emit('diffusion_step', {'diffusion_step': step})
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/make_gif", methods=["POST"])
|
||||||
|
def make_gif():
|
||||||
|
input = request.files
|
||||||
|
filename = request.form["filename"]
|
||||||
|
origin_image_bytes = input["origin_img"].read()
|
||||||
|
clean_image_bytes = input["clean_img"].read()
|
||||||
|
origin_image, _ = load_img(origin_image_bytes)
|
||||||
|
clean_image, _ = load_img(clean_image_bytes)
|
||||||
|
gif_bytes = make_compare_gif(
|
||||||
|
Image.fromarray(origin_image),
|
||||||
|
Image.fromarray(clean_image)
|
||||||
|
)
|
||||||
|
return send_file(
|
||||||
|
io.BytesIO(gif_bytes),
|
||||||
|
mimetype='image/gif',
|
||||||
|
as_attachment=True,
|
||||||
|
attachment_filename=filename
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.route("/save_image", methods=["POST"])
|
@app.route("/save_image", methods=["POST"])
|
||||||
def save_image():
|
def save_image():
|
||||||
# all image in output directory
|
# all image in output directory
|
||||||
|
Loading…
Reference in New Issue
Block a user