Spaces:
Runtime error
Runtime error
<html> | |
<head> | |
<title>Stablediffusion Infinity</title> | |
<meta charset="utf-8"> | |
<link rel="icon" type="image/x-icon" href="./favicon.png"> | |
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/lkwq007/stablediffusion-infinity@v0.1.0/css/w2ui.min.css"> | |
<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/lkwq007/stablediffusion-infinity@v0.1.0/js/w2ui.min.js"></script> | |
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css"> | |
<script src="https://cdn.jsdelivr.net/gh/lkwq007/stablediffusion-infinity@v0.1.0/js/fabric.min.js"></script> | |
<script defer src="https://cdn.jsdelivr.net/gh/lkwq007/stablediffusion-infinity@v0.1.0/js/toolbar.js"></script> | |
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" /> | |
<script defer src="https://pyscript.net/alpha/pyscript.js"></script> | |
<style> | |
#container { | |
position: relative; | |
margin:auto; | |
display: block; | |
} | |
#container > canvas { | |
position: absolute; | |
top: 0; | |
left: 0; | |
} | |
.control { | |
display: none; | |
} | |
</style> | |
</head> | |
<body> | |
<div> | |
<button type="button" class="control" id="export">Export</button> | |
<button type="button" class="control" id="outpaint">Outpaint</button> | |
<button type="button" class="control" id="undo">Undo</button> | |
<button type="button" class="control" id="commit">Commit</button> | |
<button type="button" class="control" id="transfer">Transfer</button> | |
<button type="button" class="control" id="upload">Upload</button> | |
<button type="button" class="control" id="draw">Draw</button> | |
<input type="text" id="mode" value="selection" class="control"> | |
<input type="text" id="setup" value="0" class="control"> | |
<input type="text" id="upload_content" value="0" class="control"> | |
<textarea rows="1" id="selbuffer" name="selbuffer" class="control"></textarea> | |
<fieldset class="control"> | |
<div> | |
<input type="radio" id="mode0" name="mode" value="0" checked> | |
<label for="mode0">SelBox</label> | |
</div> | |
<div> | |
<input type="radio" id="mode1" name="mode" value="1"> | |
<label for="mode1">Image</label> | |
</div> | |
<div> | |
<input type="radio" id="mode2" name="mode" value="2"> | |
<label for="mode2">Brush</label> | |
</div> | |
</fieldset> | |
</div> | |
<div id = "outer_container"> | |
<div id = "container"> | |
<canvas id = "canvas0"></canvas> | |
<canvas id = "canvas1"></canvas> | |
<canvas id = "canvas2"></canvas> | |
<canvas id = "canvas3"></canvas> | |
<canvas id = "canvas4"></canvas> | |
<div id="overlay_container" style="pointer-events: none"> | |
<canvas id = "overlay_canvas" width="1" height="1"></canvas> | |
</div> | |
</div> | |
<input type="file" name="file" id="upload_file" accept="image/*" hidden> | |
<input type="file" name="state" id="upload_state" accept=".sdinf" hidden> | |
<div style="position: relative;"> | |
<div id="toolbar" style></div> | |
</div> | |
</div> | |
<py-env> | |
- numpy | |
- Pillow | |
- paths: | |
- ./canvas.py | |
</py-env> | |
<py-script> | |
from pyodide import to_js, create_proxy | |
from PIL import Image | |
import io | |
import time | |
import base64 | |
import numpy as np | |
from js import ( | |
console, | |
document, | |
parent, | |
devicePixelRatio, | |
ImageData, | |
Uint8ClampedArray, | |
CanvasRenderingContext2D as Context2d, | |
requestAnimationFrame, | |
window, | |
encodeURIComponent, | |
w2ui, | |
update_eraser, | |
update_scale, | |
adjust_selection, | |
update_count, | |
enable_result_lst, | |
setup_shortcut, | |
) | |
from canvas import InfCanvas | |
base_lst = [None] | |
async def draw_canvas() -> None: | |
width=1024 | |
height=600 | |
canvas=InfCanvas(1024,600) | |
update_eraser(canvas.eraser_size,min(canvas.selection_size_h,canvas.selection_size_w)) | |
document.querySelector("#container").style.height= f"{height}px" | |
document.querySelector("#container").style.width = f"{width}px" | |
canvas.setup_mouse() | |
canvas.clear_background() | |
canvas.draw_buffer() | |
canvas.draw_selection_box() | |
base_lst[0]=canvas | |
async def draw_canvas_func(): | |
width=1500 | |
height=600 | |
selection_size=256 | |
document.querySelector("#container").style.width = f"{width}px" | |
document.querySelector("#container").style.height= f"{height}px" | |
canvas=InfCanvas(int(width),int(height),selection_size=int(selection_size)) | |
canvas.setup_mouse() | |
canvas.clear_background() | |
canvas.draw_buffer() | |
canvas.draw_selection_box() | |
base_lst[0]=canvas | |
async def export_func(event): | |
base=base_lst[0] | |
arr=base.export() | |
base.draw_buffer() | |
base.canvas[2].clear() | |
base64_str = base.numpy_to_base64(arr) | |
time_str = time.strftime("%Y%m%d_%H%M%S") | |
link = document.createElement("a") | |
if len(event.data)>2 and event.data[2]: | |
filename = event.data[2] | |
else: | |
filename = f"outpaint_{time_str}" | |
# link.download = f"sdinf_state_{time_str}.json" | |
link.download = f"{filename}.png" | |
# link.download = f"outpaint_{time_str}.png" | |
link.href = "data:image/png;base64,"+base64_str | |
link.click() | |
console.log(f"Canvas saved to {filename}.png") | |
img_candidate_lst=[None,0] | |
async def outpaint_func(event): | |
base=base_lst[0] | |
if len(event.data)==2: | |
app=parent.document.querySelector("gradio-app") | |
if app.shadowRoot: | |
app=app.shadowRoot | |
base64_str_raw=app.querySelector("#output textarea").value | |
base64_str_lst=base64_str_raw.split(",") | |
img_candidate_lst[0]=base64_str_lst | |
img_candidate_lst[1]=0 | |
elif event.data[2]=="next": | |
img_candidate_lst[1]+=1 | |
elif event.data[2]=="prev": | |
img_candidate_lst[1]-=1 | |
enable_result_lst() | |
if img_candidate_lst[0] is None: | |
return | |
lst=img_candidate_lst[0] | |
idx=img_candidate_lst[1] | |
update_count(idx%len(lst)+1,len(lst)) | |
arr=base.base64_to_numpy(lst[idx%len(lst)]) | |
base.fill_selection(arr) | |
base.draw_selection_box() | |
async def undo_func(event): | |
base=base_lst[0] | |
img_candidate_lst[0]=None | |
if base.sel_dirty: | |
base.sel_buffer = np.zeros((base.selection_size_h, base.selection_size_w, 4), dtype=np.uint8) | |
base.sel_dirty = False | |
base.canvas[2].clear() | |
async def commit_func(event): | |
base=base_lst[0] | |
img_candidate_lst[0]=None | |
if base.sel_dirty: | |
base.write_selection_to_buffer() | |
base.draw_buffer() | |
base.canvas[2].clear() | |
async def transfer_func(event): | |
base=base_lst[0] | |
base.read_selection_from_buffer() | |
sel_buffer=base.sel_buffer | |
sel_buffer_str=base.numpy_to_base64(sel_buffer) | |
app=parent.document.querySelector("gradio-app") | |
if app.shadowRoot: | |
app=app.shadowRoot | |
app.querySelector("#input textarea").value=sel_buffer_str | |
app.querySelector("#proceed").click() | |
async def upload_func(event): | |
base=base_lst[0] | |
# base64_str=event.data[1] | |
base64_str=document.querySelector("#upload_content").value | |
base64_str=base64_str.split(",")[-1] | |
# base64_str=parent.document.querySelector("gradio-app").shadowRoot.querySelector("#upload textarea").value | |
arr=base.base64_to_numpy(base64_str) | |
h,w,c=base.buffer.shape | |
base.sync_to_buffer() | |
base.buffer_dirty=True | |
mask=arr[:,:,3:4].repeat(4,axis=2) | |
base.buffer[mask>0]=0 | |
# in case mismatch | |
base.buffer[0:h,0:w,:]+=arr | |
#base.buffer[yo:yo+h,xo:xo+w,0:3]=arr[:,:,0:3] | |
#base.buffer[yo:yo+h,xo:xo+w,-1]=arr[:,:,-1] | |
base.draw_buffer() | |
async def setup_shortcut_func(event): | |
setup_shortcut(event.data[1]) | |
document.querySelector("#export").addEventListener("click",create_proxy(export_func)) | |
document.querySelector("#undo").addEventListener("click",create_proxy(undo_func)) | |
document.querySelector("#commit").addEventListener("click",create_proxy(commit_func)) | |
document.querySelector("#outpaint").addEventListener("click",create_proxy(outpaint_func)) | |
document.querySelector("#upload").addEventListener("click",create_proxy(upload_func)) | |
document.querySelector("#transfer").addEventListener("click",create_proxy(transfer_func)) | |
document.querySelector("#draw").addEventListener("click",create_proxy(draw_canvas_func)) | |
async def setup_func(): | |
document.querySelector("#setup").value="1" | |
async def reset_func(event): | |
base=base_lst[0] | |
base.reset() | |
async def load_func(event): | |
base=base_lst[0] | |
base.load(event.data[1]) | |
async def save_func(event): | |
base=base_lst[0] | |
json_str=base.save() | |
time_str = time.strftime("%Y%m%d_%H%M%S") | |
link = document.createElement("a") | |
if len(event.data)>2 and event.data[2]: | |
filename = str(event.data[2]).strip() | |
else: | |
filename = f"outpaint_{time_str}" | |
# link.download = f"sdinf_state_{time_str}.json" | |
link.download = f"{filename}.sdinf" | |
link.href = "data:text/json;charset=utf-8,"+encodeURIComponent(json_str) | |
link.click() | |
async def prev_result_func(event): | |
base=base_lst[0] | |
base.reset() | |
async def next_result_func(event): | |
base=base_lst[0] | |
base.reset() | |
async def zoom_in_func(event): | |
base=base_lst[0] | |
scale=base.scale | |
if scale>=0.2: | |
scale-=0.1 | |
if len(event.data)>2: | |
base.update_scale(scale,int(event.data[2]),int(event.data[3])) | |
else: | |
base.update_scale(scale) | |
scale=base.scale | |
update_scale(f"{base.width}x{base.height} ({round(100/scale)}%)") | |
async def zoom_out_func(event): | |
base=base_lst[0] | |
scale=base.scale | |
if scale<10: | |
scale+=0.1 | |
console.log(len(event.data)) | |
if len(event.data)>2: | |
base.update_scale(scale,int(event.data[2]),int(event.data[3])) | |
else: | |
base.update_scale(scale) | |
scale=base.scale | |
update_scale(f"{base.width}x{base.height} ({round(100/scale)}%)") | |
async def sync_func(event): | |
base=base_lst[0] | |
base.sync_to_buffer() | |
base.canvas[2].clear() | |
async def eraser_size_func(event): | |
base=base_lst[0] | |
eraser_size=min(int(event.data[1]),min(base.selection_size_h,base.selection_size_w)) | |
eraser_size=max(8,eraser_size) | |
base.eraser_size=eraser_size | |
async def resize_selection_func(event): | |
base=base_lst[0] | |
cursor=base.cursor | |
if len(event.data)>3: | |
console.log(event.data) | |
base.cursor[0]=int(event.data[1]) | |
base.cursor[1]=int(event.data[2]) | |
base.selection_size_w=int(event.data[3])//8*8 | |
base.selection_size_h=int(event.data[4])//8*8 | |
base.refine_selection() | |
base.draw_selection_box() | |
elif len(event.data)>2: | |
base.draw_selection_box() | |
else: | |
base.canvas[-1].clear() | |
adjust_selection(cursor[0],cursor[1],base.selection_size_w,base.selection_size_h) | |
async def eraser_func(event): | |
base=base_lst[0] | |
if event.data[1]!="eraser": | |
base.canvas[-2].clear() | |
else: | |
x,y=base.mouse_pos | |
base.draw_eraser(x,y) | |
async def resize_func(event): | |
base=base_lst[0] | |
width=int(event.data[1]) | |
height=int(event.data[2]) | |
if width>=256 and height>=256: | |
if max(base.selection_size_h,base.selection_size_w)>min(width,height): | |
base.selection_size_h=256 | |
base.selection_size_w=256 | |
base.resize(width,height) | |
async def message_func(event): | |
if event.data[0]=="click": | |
if event.data[1]=="clear": | |
await reset_func(event) | |
elif event.data[1]=="save": | |
await save_func(event) | |
elif event.data[1]=="export": | |
await export_func(event) | |
elif event.data[1]=="accept": | |
await commit_func(event) | |
elif event.data[1]=="cancel": | |
await undo_func(event) | |
elif event.data[1]=="zoom_in": | |
await zoom_in_func(event) | |
elif event.data[1]=="zoom_out": | |
await zoom_out_func(event) | |
elif event.data[0]=="sync": | |
await sync_func(event) | |
elif event.data[0]=="load": | |
await load_func(event) | |
elif event.data[0]=="upload": | |
await upload_func(event) | |
elif event.data[0]=="outpaint": | |
await outpaint_func(event) | |
elif event.data[0]=="mode": | |
if event.data[1]!="selection": | |
await sync_func(event) | |
await eraser_func(event) | |
document.querySelector("#mode").value=event.data[1] | |
elif event.data[0]=="transfer": | |
await transfer_func(event) | |
elif event.data[0]=="setup": | |
await draw_canvas_func(event) | |
elif event.data[0]=="eraser_size": | |
await eraser_size_func(event) | |
elif event.data[0]=="resize_selection": | |
await resize_selection_func(event) | |
elif event.data[0]=="shortcut": | |
await setup_shortcut_func(event) | |
elif event.data[0]=="resize": | |
await resize_func(event) | |
window.addEventListener("message",create_proxy(message_func)) | |
import asyncio | |
_ = await asyncio.gather( | |
setup_func(),draw_canvas_func() | |
) | |
</py-script> | |
</body> | |
</html> | |