add generate gif button
This commit is contained in:
parent
d4ec1208ae
commit
780517b91a
@ -1,5 +1,5 @@
|
||||
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}`
|
||||
|
||||
@ -223,3 +223,34 @@ export async function downloadToOutput(
|
||||
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
|
||||
<Coffee />
|
||||
</div>
|
||||
</Button>
|
||||
</a>
|
||||
|
@ -95,10 +95,8 @@
|
||||
}
|
||||
|
||||
.editor-toolkit-btns {
|
||||
grid-area: toolkit-btns;
|
||||
display: grid;
|
||||
grid-auto-flow: column;
|
||||
column-gap: 1rem;
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.brush-shape {
|
||||
|
@ -16,10 +16,11 @@ import {
|
||||
TransformComponent,
|
||||
TransformWrapper,
|
||||
} from 'react-zoom-pan-pinch'
|
||||
import { useRecoilState, useRecoilValue } from 'recoil'
|
||||
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
|
||||
import { useWindowSize, useKey, useKeyPressEvent } from 'react-use'
|
||||
import inpaint, {
|
||||
downloadToOutput,
|
||||
makeGif,
|
||||
postInteractiveSeg,
|
||||
} from '../../adapters/inpainting'
|
||||
import Button from '../shared/Button'
|
||||
@ -40,6 +41,7 @@ import {
|
||||
croperState,
|
||||
enableFileManagerState,
|
||||
fileState,
|
||||
gifImageState,
|
||||
imageHeightState,
|
||||
imageWidthState,
|
||||
interactiveSegClicksState,
|
||||
@ -66,6 +68,7 @@ import FileSelect from '../FileSelect/FileSelect'
|
||||
import InteractiveSeg from '../InteractiveSeg/InteractiveSeg'
|
||||
import InteractiveSegConfirmActions from '../InteractiveSeg/ConfirmActions'
|
||||
import InteractiveSegReplaceModal from '../InteractiveSeg/ReplaceModal'
|
||||
import MakeGIF from './MakeGIF'
|
||||
|
||||
const TOOLBAR_SIZE = 200
|
||||
const MIN_BRUSH_SIZE = 10
|
||||
@ -112,7 +115,7 @@ export default function Editor() {
|
||||
const settings = useRecoilValue(settingState)
|
||||
const [seedVal, setSeed] = useRecoilState(seedState)
|
||||
const croperRect = useRecoilValue(croperState)
|
||||
const [toastVal, setToastState] = useRecoilState(toastState)
|
||||
const setToastState = useSetRecoilState(toastState)
|
||||
const [isInpainting, setIsInpainting] = useRecoilState(isInpaintingState)
|
||||
const runMannually = useRecoilValue(runManuallyState)
|
||||
const isSD = useRecoilValue(isSDState)
|
||||
@ -181,8 +184,8 @@ export default function Editor() {
|
||||
const [redoLineGroups, setRedoLineGroups] = useState<LineGroup[]>([])
|
||||
const enableFileManager = useRecoilValue(enableFileManagerState)
|
||||
|
||||
const [imageWidth, setImageWidth] = useRecoilState(imageWidthState)
|
||||
const [imageHeight, setImageHeight] = useRecoilState(imageHeightState)
|
||||
const setImageWidth = useSetRecoilState(imageWidthState)
|
||||
const setImageHeight = useSetRecoilState(imageHeightState)
|
||||
const app = useRecoilValue(appState)
|
||||
|
||||
const draw = useCallback(
|
||||
@ -1534,6 +1537,7 @@ export default function Editor() {
|
||||
}}
|
||||
disabled={renders.length === 0}
|
||||
/>
|
||||
<MakeGIF renders={renders} />
|
||||
<Button
|
||||
toolTip="Save Image"
|
||||
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 { useRecoilState } from 'recoil'
|
||||
import { Cog6ToothIcon } from '@heroicons/react/24/outline'
|
||||
import { settingState } from '../../store/Atoms'
|
||||
import Button from '../shared/Button'
|
||||
|
||||
@ -16,29 +17,7 @@ const SettingIcon = () => {
|
||||
onClick={onClick}
|
||||
toolTip="Settings"
|
||||
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>
|
||||
}
|
||||
icon={<Cog6ToothIcon />}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
|
@ -3,7 +3,7 @@ import * as ToastPrimitive from '@radix-ui/react-toast'
|
||||
import { ToastProps } from '@radix-ui/react-toast'
|
||||
import { CheckIcon, ExclamationCircleIcon } from '@heroicons/react/24/outline'
|
||||
|
||||
const LoadingIcon = () => {
|
||||
export const LoadingIcon = () => {
|
||||
return (
|
||||
<span className="loading-icon">
|
||||
<svg
|
||||
|
@ -45,6 +45,7 @@ interface AppState {
|
||||
interactiveSegClicks: number[][]
|
||||
showFileManager: boolean
|
||||
enableFileManager: boolean
|
||||
gifImage: HTMLImageElement | undefined
|
||||
}
|
||||
|
||||
export const appState = atom<AppState>({
|
||||
@ -61,6 +62,7 @@ export const appState = atom<AppState>({
|
||||
interactiveSegClicks: [],
|
||||
showFileManager: 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({
|
||||
key: 'fileState',
|
||||
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.reverse()
|
||||
|
||||
max_side_length = min(max_side_length, max(clean_img.size))
|
||||
|
||||
src_img = keep_ratio_resize(src_img, max_side_length)
|
||||
clean_img = keep_ratio_resize(clean_img, max_side_length)
|
||||
width, height = src_img.size
|
||||
|
@ -19,6 +19,7 @@ from loguru import logger
|
||||
from watchdog.events import FileSystemEventHandler
|
||||
|
||||
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.schema import Config
|
||||
from lama_cleaner.file_manager import FileManager
|
||||
@ -93,6 +94,26 @@ def diffuser_callback(i, t, latents):
|
||||
# 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"])
|
||||
def save_image():
|
||||
# all image in output directory
|
||||
|
Loading…
Reference in New Issue
Block a user