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> | |