capstone / index.html
stanley
closing accord
0682467
<!DOCTYPE html>
<html>
<head>
<title>stanley capstone</title>
<meta charset="utf-8">
<link rel="stylesheet" type="text/css" href="https://cdn.jsdelivr.net/gh/stanleywalker1/capstone-studio-2@main/css/w2ui.min.css">
<script type="text/javascript" src="https://cdn.jsdelivr.net/gh/stanleywalker1/capstone-studio-2@main/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/stanleywalker1/capstone-studio-2@main/js/fabric.min.js"></script>
<script defer src="https://cdn.jsdelivr.net/gh/stanleywalker1/capstone-studio-2@latest/js/toolbar6.js"></script>
<link rel="stylesheet" href="https://pyscript.net/alpha/pyscript.css" />
<script defer src="https://pyscript.net/alpha/pyscript.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-analytics.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-storage.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.10.0/firebase-database.js"></script>
<style>
html, body {
width: 100%;
height: 100%;
margin: 0;
padding: 0;
overflow: hidden;
}
#container {
position: relative;
margin:auto;
display: block;
margin-bottom: 5px;
}
#container > canvas {
position: absolute;
top: 0;
left: 0;
}
.control {
display: none;
background-color: aliceblue;
}
#outer_container {
width: 100%;
height: 100vh;
}
#hamburger-menu {
position: fixed;
top: 10px;
right: 10px;
width: 50px;
height: 50px;
background-color: #f1f1f1;
border-radius: 50%;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
z-index: 1000;
overflow: hidden;
}
#hamburger-menu::before {
content: "";
position: absolute;
top: -50%;
left: -50%;
width: 200%;
height: 200%;
background-image: radial-gradient(circle, #00ff00, #00ffff, #ff00ff, #ff0000, #ffff00, #00ff00);
background-size: 300% 300%;
animation: gradient-animation 6s linear infinite;
z-index: -1;
}
#hamburger-menu i {
font-size: 24px;
position: relative;
z-index: 1;
}
.eye-icon {
position: relative;
display: inline-block;
width: 24px;
height: 12px;
background-color: transparent;
}
.eye-icon::before,
.eye-icon::after {
content: "";
position: absolute;
width: 8px;
height: 8px;
background-color: transparent;
border: 2px solid black;
border-radius: 50%;
}
.eye-icon::before {
left: 4px;
top: 2px;
}
.eye-icon::after {
right: 4px;
top: 2px;
}
@keyframes gradient-animation {
0% {
background-position: 0% 50%;
}
50% {
background-position: 100% 50%;
}
100% {
background-position: 0% 50%;
}
}
#toolbar {
display: block;
height: 200px;
background: #0C0F19;
}
.w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button-hover {
background-color: #713870; /* Change this to the desired hover background color */
}
#popup.popup-hidden {
display: none;
}
#popup {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 2000;
}
#popup-content {
background-color: #1F2937; /* Nighttime mode background color */
padding: 20px;
border-radius: 10px;
max-width: 80%;
max-height: 80%;
overflow-y: auto;
color: #ffffff; /* Nighttime mode text color */
}
#popup-content h2 {
font-weight: bold;
font-size: 16px;
background-color: #6B46C1;
border-radius: 10px;
padding: 5px;
color: #ffffff;
display: inline-block;
}
#prompt-list {
list-style-type: none;
padding: 0;
margin: 0;
}
.prompt-item {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
padding: 5px 0;
}
.prompt-item span {
max-width: 68%;
word-wrap: break-word;
}
.prompt-item:nth-child(even) {
background-color: #2D3748; /* Nighttime mode alternate row color */
}
</style>
</head>
<body>
<div>
<div id="hamburger-menu">
<i class="fa fa-eye" aria-hidden="true"></i>
</div>
<div id="popup" class="popup-hidden">
<div id="popup-content">
<h2>Prompt History</h2>
<ul id="prompt-list"></ul>
</div>
</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>
<script>
// alert("starting js");
function togglePopup() {
const popup = document.getElementById("popup");
const hamburgerMenu = document.getElementById("hamburger-menu");
if (popup.classList.contains("popup-hidden")) {
popup.classList.remove("popup-hidden");
hamburgerMenu.classList.add("open");
fetchPromptsFromFirebase();
} else {
popup.classList.add("popup-hidden");
hamburgerMenu.classList.remove("open");
}
}
function fetchPromptsFromFirebase() {
const promptList = document.getElementById("prompt-list");
const database = firebase.database();
const inputsRef = database.ref("inputs");
inputsRef.on("value", (snapshot) => {
const prompts = snapshot.val();
promptList.innerHTML = "";
const keys = Object.keys(prompts).reverse();
for (const key of keys) {
const promptItem = document.createElement("li");
promptItem.classList.add("prompt-item");
const promptText = document.createElement("span");
promptText.textContent = prompts[key].prompt;
const timestamp = document.createElement("span");
timestamp.textContent = new Date(prompts[key].timestamp * 1000).toLocaleString();
promptItem.appendChild(promptText);
promptItem.appendChild(timestamp);
promptList.appendChild(promptItem);
}
});
}
function aws(name, x, y) {
return `coming from javascript ${name} ${x} ${y}`;
}
const { initializeApp } = firebase;
const { getStorage, ref, listAll, getDownloadURL, getMetadata, uploadBytesResumable } = firebase.storage;
const firebaseConfig = {
apiKey: "AIzaSyCxG7s_Wg6RAC4AQ5ZpkCgt0XcnSqcwt-A",
authDomain: "nyucapstone-7c22c.firebaseapp.com",
projectId: "nyucapstone-7c22c",
storageBucket: "nyucapstone-7c22c.appspot.com",
messagingSenderId: "658619789110",
appId: "1:658619789110:web:4eb43edacd4bbfcca74d97",
measurementId: "G-NCNE4TC0GC",
databaseURL: "https://nyucapstone-7c22c-default-rtdb.firebaseio.com/",
};
const fireapp = initializeApp(firebaseConfig);
function uploadImageToFirebase(base64_str, time_str) {
return new Promise((resolve, reject) => {
//alert("starting to upload");
const atob = (str) => {
return window.atob(str);
};
const byteCharacters = atob(base64_str);
const byteNumbers = new Uint8Array(byteCharacters.length);
for (let i = 0; i < byteCharacters.length; i++) {
byteNumbers[i] = byteCharacters.charCodeAt(i);
}
const analytics = firebase.analytics();
const byteArray = new Uint8Array(byteNumbers);
const blob = new Blob([byteArray], {type: "image/png"});
const storage = firebase.storage(fireapp);
const storageRef = firebase.storage().ref(`images/${time_str}.png`);
const uploadTask = storageRef.put(blob);
alert("saved to cloud");
// Replace the successful upload handler with this:
uploadTask.on("state_changed", (snapshot) => {
// Handle the progress of the upload
}, (error) => {
// Handle the error during the upload
reject(error);
}, async () => {
// Handle the successful upload
const database = firebase.database();
const latestImageRef = database.ref("latestImage");
const downloadURL = await storageRef.getDownloadURL();
await latestImageRef.set({
fileName: `${time_str}.png`,
downloadURL: downloadURL
});
resolve();
});
});
}
// UI patch, aim to remove this and replace all elements with pure Gradio
function overrideW2uiStyles() {
const toolbar = document.querySelector(".w2ui-toolbar");
if (!toolbar) {
setTimeout(overrideW2uiStyles, 200); // Check again after 200ms
return;
}
toolbar.style.backgroundColor = "#0C0F19";
const toolbarButtons = document.querySelectorAll(".w2ui-toolbar .w2ui-scroll-wrapper .w2ui-tb-button");
toolbarButtons.forEach(button => {
button.style.border = "4px solid rgb(67, 55, 201)";
button.style.backgroundColor = "#1F2937";
button.style.color = "#ffffff";
button.style.fontSize = "16px";
button.style.fontWeight = 600;
const nestedText = button.querySelector("div.w2ui-tb-text:nth-child(1)");
if (nestedText) {
nestedText.style.color = "rgb(255, 255, 255)";
}
// Add event listeners for mouseover and mouseout events
button.addEventListener("mouseenter", () => {
button.style.backgroundColor = "#4B5563";
});
button.addEventListener("mouseleave", () => {
button.style.backgroundColor = "#1F2937";
});
});
const outpaintElement = document.getElementById("tb_toolbar_item_outpaint");
if (!outpaintElement) {
setTimeout(overrideW2uiStyles, 200); // Check again after 200ms
return;
}
const nestedDiv1 = outpaintElement.querySelector("#tb_toolbar_item_outpaint > div:nth-child(2)");
const nestedDiv2 = outpaintElement.querySelector("div.w2ui-tb-icon span.fa-solid.fa-wand-magic-sparkles");
if (nestedDiv1) {
nestedDiv1.style.color = "white";
}
if (nestedDiv2) {
nestedDiv2.style.fontSize = "30px";
}
outpaintElement.style.height = "70px";
outpaintElement.style.border = "4px solid rgb(67, 55, 201)";
outpaintElement.style.backgroundColor = "rgb(31, 41, 55)";
outpaintElement.style.color = "rgb(255, 255, 255)";
outpaintElement.style.fontSize = "16px";
outpaintElement.style.fontWeight = "600";
outpaintElement.style.width = "200px";
outpaintElement.style.lineHeight = "50px";
outpaintElement.style.textAlign = "center";
outpaintElement.style.borderRadius = "40px";
outpaintElement.style.display = "flex";
outpaintElement.style.alignItems = "center";
outpaintElement.style.justifyContent = "center";
const devButtonElement = document.getElementById("tb_toolbar_item_developer_options");
if (!devButtonElement) {
setTimeout(overrideW2uiStyles, 200); // Check again after 200ms
return;
}
devButtonElement.style.height = "40px";
devButtonElement.style.backgroundColor = "rgb(31, 41, 55)";
devButtonElement.style.color = "rgb(255, 255, 255)";
devButtonElement.style.fontSize = "16px";
devButtonElement.style.fontWeight = "600";
devButtonElement.style.width = "200px";
devButtonElement.style.lineHeight = "50px";
devButtonElement.style.textAlign = "center";
devButtonElement.style.borderRadius = "40px";
devButtonElement.style.display = "flex";
devButtonElement.style.alignItems = "center";
devButtonElement.style.justifyContent = "center";
const zoomOutButtonElement = document.getElementById("tb_toolbar_item_zoom_out");
if (!zoomOutButtonElement) {
setTimeout(overrideW2uiStyles, 200); // Check again after 200ms
return;
}
zoomOutButtonElement.style.height = "45px";
zoomOutButtonElement.style.width = "45px";
zoomOutButtonElement.style.backgroundColor = "rgb(31, 41, 55)";
zoomOutButtonElement.style.color = "rgb(255, 255, 255)";
zoomOutButtonElement.style.fontSize = "16px";
zoomOutButtonElement.style.fontWeight = "600";
zoomOutButtonElement.style.lineHeight = "50px";
zoomOutButtonElement.style.textAlign = "center";
zoomOutButtonElement.style.borderRadius = "40px";
zoomOutButtonElement.style.display = "flex";
zoomOutButtonElement.style.alignItems = "center";
zoomOutButtonElement.style.justifyContent = "center";
const zoomInButtonElement = document.getElementById("tb_toolbar_item_zoom_in");
if (!zoomInButtonElement) {
setTimeout(overrideW2uiStyles, 200); // Check again after 200ms
return;
}
zoomInButtonElement.style.height = "45px";
zoomInButtonElement.style.width = "45px";
zoomInButtonElement.style.backgroundColor = "rgb(31, 41, 55)";
zoomInButtonElement.style.color = "rgb(255, 255, 255)";
zoomInButtonElement.style.fontSize = "16px";
zoomInButtonElement.style.fontWeight = "600";
zoomInButtonElement.style.lineHeight = "50px";
zoomInButtonElement.style.textAlign = "center";
zoomInButtonElement.style.borderRadius = "40px";
zoomInButtonElement.style.display = "flex";
zoomInButtonElement.style.alignItems = "center";
zoomInButtonElement.style.justifyContent = "center";
const acceptButtonElement = document.getElementById("tb_toolbar_item_accept");
const acceptNestedText = acceptButtonElement.querySelector(".w2ui-tb-button.disabled.hidden div.w2ui-tb-text");
if (!acceptButtonElement) {
setTimeout(overrideW2uiStyles, 200); // Check again after 200ms
return;
}
if (!acceptNestedText) {
setTimeout(overrideW2uiStyles, 200); // Check again after 200ms
return;
}
acceptButtonElement.style.height = "45px";
acceptButtonElement.style.width = "100px";
acceptButtonElement.style.backgroundColor = "rgb(31, 41, 55)";
acceptButtonElement.style.color = "rgb(255, 255, 255)";
acceptButtonElement.style.fontSize = "16px";
acceptButtonElement.style.fontWeight = "600";
acceptButtonElement.style.lineHeight = "50px";
acceptButtonElement.style.textAlign = "center";
acceptButtonElement.style.borderRadius = "40px";
acceptButtonElement.style.display = "flex";
acceptButtonElement.style.alignItems = "center";
acceptButtonElement.style.justifyContent = "center";
acceptNestedText.style.color = "rgb(255, 255, 255)";
acceptNestedText.style.marginLeft = "0";
const cancelButtonElement = document.getElementById("tb_toolbar_item_cancel");
if (!cancelButtonElement) {
setTimeout(overrideW2uiStyles, 200); // Check again after 200ms
return;
}
const cancelNestedText = cancelButtonElement.querySelector("div.w2ui-tb-text");
if (!cancelNestedText) {
setTimeout(overrideW2uiStyles, 200); // Check again after 200ms
return;
}
cancelButtonElement.style.height = "45px";
cancelButtonElement.style.width = "100px";
cancelButtonElement.style.backgroundColor = "rgb(31, 41, 55)";
cancelButtonElement.style.color = "rgb(255, 255, 255)";
cancelButtonElement.style.fontSize = "16px";
cancelButtonElement.style.fontWeight = "600";
cancelButtonElement.style.lineHeight = "50px";
cancelButtonElement.style.textAlign = "center";
cancelButtonElement.style.borderRadius = "40px";
cancelButtonElement.style.display = "flex";
cancelButtonElement.style.alignItems = "center";
cancelButtonElement.style.justifyContent = "center";
cancelNestedText.style.color = "rgb(255, 255, 255)";
cancelNestedText.style.marginLeft = "0";
}
// Execute style function to start checking
overrideW2uiStyles();
document.getElementById("hamburger-menu").addEventListener("click", togglePopup);
document.getElementById("popup").addEventListener("click", (event) => {
if (event.target === event.currentTarget) {
togglePopup();
}
});
// alert("js loaded");
</script>
<py-env>
- numpy
- Pillow
- micropip:
- boto3
- paths:
- ./canvas.py
</py-env>
<py-script>
from pyodide import to_js, create_proxy
from PIL import Image
import io
import time
import base64
from collections import deque
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,
update_undo_redo,
alert,
uploadImageToFirebase,
firebase,
aws,
fetch,
overrideW2uiStyles
)
answer = aws("hello", 1, 2)
console.log(answer)
#addPhoto("demo")
# async def get_latest_image_from_firebase():
# alert("get_latest_image_from_firebase called")
# try:
# database = firebase.database()
# alert("try called")
# latestImageRef = database.ref("latestImage")
# latestImageSnapshot = await latestImageRef.once("value")
# latestImageInfo = latestImageSnapshot.val()
# download_url = latestImageInfo["downloadURL"]
# with pyodide.open_url(download_url) as f:
# img = Image.open(f)
# print("Downloaded image:", str(img))
# return img
# except Exception as e:
# print("Error while getting the latest image from Firebase:", str(e))
# return None
async def fetch_latest_image_url(database_url):
console.log("fetch_latest_image called")
# different methods to call
response = await fetch(f"{database_url}/latestImage.json")
console.log(f"response status: {response.status}, status text: {response.statusText}")
latest_image_data = await response.json()
latest_image_data = latest_image_data.to_py()
image_url = latest_image_data["downloadURL"]
image_name = latest_image_data["fileName"]
console.log(f"Latest image URL: {image_url}")
console.log(f"Latest image name: {image_name}")
# Fetch the image data as ArrayBuffer
image_response = await fetch(image_url)
image_data = await image_response.arrayBuffer()
return image_data, image_name
from canvas import InfCanvas
class History:
def __init__(self,maxlen=10):
self.idx=-1
self.undo_lst=deque([],maxlen=maxlen)
self.redo_lst=deque([],maxlen=maxlen)
self.state=None
def undo(self):
cur=None
if len(self.undo_lst):
cur=self.undo_lst.pop()
self.redo_lst.appendleft(cur)
return cur
def redo(self):
cur=None
if len(self.redo_lst):
cur=self.redo_lst.popleft()
self.undo_lst.append(cur)
return cur
def check(self):
return len(self.undo_lst)>0,len(self.redo_lst)>0
def append(self,state,update=True):
self.redo_lst.clear()
self.undo_lst.append(state)
if update:
update_undo_redo(*self.check())
history = History()
base_lst = [None]
async def draw_canvas() -> None:
# alert("draw_canvas called")
width=1024
height=700
canvas=InfCanvas(1024,700)
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
# latest_image = await get_latest_image_from_firebase()
# if latest_image is not None:
# Log the URL of the latest image to the console
# console.log(f"Latest image URL: {latest_image.url}")
# Request the parent window to display the latest image on the canvas
# (commented out to fix the indentation error)
# window.parent.postMessage({ type: "displayLatestImageOnCanvas", image: latest_image }, "*")
# else:
# print("No latest image found in Firebase.")
# new draw_canvas scale method
from PIL import ImageOps
async def draw_canvas_func(event):
# alert("draw_canvas gradio called")
database_url = "https://nyucapstone-7c22c-default-rtdb.firebaseio.com"
image_data, latest_image_name = await fetch_latest_image_url(database_url)
pil_image = Image.open(io.BytesIO(image_data.to_py()))
np_image = np.array(pil_image)
# Get the dimensions of the input image
img_height, img_width, _ = np_image.shape
# Set the desired display dimensions
display_width = 1024
display_height = 768
# Calculate the zoom level based on the input image dimensions and the desired display dimensions
zoom_level = min(display_width / img_width, display_height / img_height)
# Pad the input image to fit the canvas buffer while maintaining the aspect ratio
padded_image = ImageOps.pad(pil_image, (int(display_width), int(display_height)), Image.ANTIALIAS, color=(0, 0, 0, 0))
padded_np_image = np.array(padded_image)
# Set the canvas dimensions to match the desired display dimensions
width = display_width
height = display_height
# selection_size = min(width, height) // 2
selection_size = int(min(img_width, img_height) * 0.25)
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), firebase_image_data=padded_np_image)
canvas.setup_mouse()
canvas.clear_background()
canvas.draw_buffer()
canvas.draw_selection_box()
# Update the canvas buffer with the padded image data and redraw the buffer
h, w, c = canvas.buffer.shape
canvas.sync_to_buffer()
canvas.buffer_dirty = True
# Create a mask using the padded image shape
mask = padded_np_image[:, :, 3:4].repeat(4, axis=2)
canvas.buffer[mask > 0] = 0
canvas.buffer[:height, :width] += padded_np_image
canvas.draw_buffer()
base_lst[0] = canvas
# alert("made it to end of draw_canvas gradio")
# original draw_canvas
async def test_canvas_func(event):
# alert("draw_canvas gradio called")
try:
app=parent.document.querySelector("gradio-app")
if app.shadowRoot:
app=app.shadowRoot
width=app.querySelector("#canvas_width input").value
height=app.querySelector("#canvas_height input").value
selection_size=app.querySelector("#selection_size input").value
except:
width=1024
height=768
selection_size=384
document.querySelector("#container").style.width = f"{width}px"
document.querySelector("#container").style.height= f"{height}px"
database_url = "https://nyucapstone-7c22c-default-rtdb.firebaseio.com"
image_data, latest_image_name = await fetch_latest_image_url(database_url)
pil_image = Image.open(io.BytesIO(image_data.to_py()))
np_image = np.array(pil_image)
canvas=InfCanvas(int(width),int(height),selection_size=int(selection_size),firebase_image_data=np_image)
canvas.setup_mouse()
canvas.clear_background()
canvas.draw_buffer()
canvas.draw_selection_box()
# Update the canvas buffer with the new image data and redraw the buffer
h, w, c = canvas.buffer.shape
canvas.sync_to_buffer()
canvas.buffer_dirty = True
h_min = min(h, np_image.shape[0])
w_min = min(w, np_image.shape[1])
# mask = np_image[:, :, 3:4].repeat(4, axis=2)
# canvas.buffer[mask > 0] = 0
# canvas.buffer[0:h, 0:w, :] += np_image
mask = np_image[:h_min, :w_min, 3:4].repeat(4, axis=2)
canvas.buffer[:h_min, :w_min][mask > 0] = 0
canvas.buffer[:h_min, :w_min] += np_image[:h_min, :w_min]
canvas.draw_buffer()
base_lst[0]=canvas
# alert("made it to end of draw_canvas gradio")
import js
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")
# The rest of the original export_func code
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"{filename}.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()
js.overrideW2uiStyles()
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()
if len(event.data) > 2:
history.append(base.save())
# sending the image to firebase here
arr = base.export()
base64_str = base.numpy_to_base64(arr)
time_str = time.strftime("%Y%m%d_%H%M%S")
# Call the JavaScript function to upload the image to Firebase storage
await js.uploadImageToFirebase(base64_str, time_str)
async def history_undo_func(event):
base=base_lst[0]
if base.buffer_dirty or len(history.redo_lst)>0:
state=history.undo()
else:
history.undo()
state=history.undo()
if state is not None:
base.load(state)
update_undo_redo(*history.check())
async def history_setup_func(event):
base=base_lst[0]
history.undo_lst.clear()
history.redo_lst.clear()
history.append(base.save(),update=False)
async def history_redo_func(event):
base=base_lst[0]
if len(history.undo_lst)>0:
state=history.redo()
else:
history.redo()
state=history.redo()
if state is not None:
base.load(state)
update_undo_redo(*history.check())
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]
# Retrieve the base64 encoded image string from the #upload_content HTML element
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()
if len(event.data)>2:
history.append(base.save())
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)}%)")
js.overrideW2uiStyles()
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)}%)")
js.overrideW2uiStyles()
async def sync_func(event):
base=base_lst[0]
base.sync_to_buffer()
base.canvas[2].clear()
js.overrideW2uiStyles()
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[1]=="redo":
await history_redo_func(event)
elif event.data[1]=="undo":
await history_undo_func(event)
elif event.data[1]=="history":
await history_setup_func(event)
js.overrideW2uiStyles()
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)
js.overrideW2uiStyles()
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()
)
</py-script>
</body>
</html>