lnyan's picture
Update to v0.1.0
a5b25be
<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/[email protected]/css/w2ui.min.css">
<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/lkwq007/[email protected]/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/[email protected]/js/fabric.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/gh/lkwq007/[email protected]/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>