Spaces:
Runtime error
Runtime error
<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> | |