add generate gif button

This commit is contained in:
Qing 2023-01-27 20:12:04 +08:00
parent d4ec1208ae
commit 780517b91a
10 changed files with 196 additions and 34 deletions

View File

@ -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}`)
}
}

View File

@ -70,7 +70,6 @@ const CoffeeIcon = () => {
}} }}
> >
Sure Sure
<Coffee />
</div> </div>
</Button> </Button>
</a> </a>

View File

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

View File

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

View 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

View File

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

View File

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

View File

@ -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 }) => {

View File

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

View File

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