From 3fd062290a53d3fa78b911e317995508898137db Mon Sep 17 00:00:00 2001 From: tim h Date: Tue, 29 Nov 2022 19:59:16 -0600 Subject: [PATCH 1/3] attempt at updating prettier action --- {.github.bkp => .github}/workflows/autoformat.yml | 1 + 1 file changed, 1 insertion(+) rename {.github.bkp => .github}/workflows/autoformat.yml (90%) diff --git a/.github.bkp/workflows/autoformat.yml b/.github/workflows/autoformat.yml similarity index 90% rename from .github.bkp/workflows/autoformat.yml rename to .github/workflows/autoformat.yml index ea58136..e2ed9bf 100644 --- a/.github.bkp/workflows/autoformat.yml +++ b/.github/workflows/autoformat.yml @@ -13,4 +13,5 @@ jobs: - name: Prettify uses: creyD/prettier_action@v4.2 with: + ref: ${{ github.head_ref }} prettier_options: --write **/*.{js,html,css,md} From b2f110bc572f279736120afb72d355a18e682709 Mon Sep 17 00:00:00 2001 From: tim h Date: Tue, 29 Nov 2022 20:08:22 -0600 Subject: [PATCH 2/3] test in prod, good plan --- .github/workflows/autoformat.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/autoformat.yml b/.github/workflows/autoformat.yml index e2ed9bf..bad7bd1 100644 --- a/.github/workflows/autoformat.yml +++ b/.github/workflows/autoformat.yml @@ -1,5 +1,8 @@ name: Prettier Autoformatting on: + push: + branches: + - "main" pull_request: branches: [main] types: [opened, synchronize, closed] From d33edaa8cf1bd683e130a8708aa1d33175e7c221 Mon Sep 17 00:00:00 2001 From: zero01101 Date: Wed, 30 Nov 2022 02:08:43 +0000 Subject: [PATCH 3/3] Prettified Code! --- index.html | 610 +++++++-------- js/index.js | 2172 +++++++++++++++++++++++++-------------------------- 2 files changed, 1391 insertions(+), 1391 deletions(-) diff --git a/index.html b/index.html index dffa80b..04f6a77 100644 --- a/index.html +++ b/index.html @@ -1,305 +1,305 @@ - - - - - openOutpaint 🐠 - - - - - - - - - - - - - - - - - -
-
- openOutpaint 🐠 -
- -
- - -
-
History
- -
- - -
-
- -
-
-
-
- - -
-
-
- - -

lol ur browser sucks

-
- - -

lol ur browser sucks

-
- - -

lol ur browser sucks

-
- - -

lol ur browser sucks

-
- - -

lol ur browser sucks

-
- - -

lol ur browser sucks

-
-
- -
-
-
-
-
- - -

lol ur browser sucks

-
-
- -

lol ur browser sucks

-
-
-
-
-
- - - - - - - - - - - - - - - - - - - - - + + + + + openOutpaint 🐠 + + + + + + + + + + + + + + + + + +
+
+ openOutpaint 🐠 +
+ +
+ + +
+
History
+ +
+ + +
+
+ +
+
+
+
+ + +
+
+
+ + +

lol ur browser sucks

+
+ + +

lol ur browser sucks

+
+ + +

lol ur browser sucks

+
+ + +

lol ur browser sucks

+
+ + +

lol ur browser sucks

+
+ + +

lol ur browser sucks

+
+
+ +
+
+
+
+
+ + +

lol ur browser sucks

+
+
+ +

lol ur browser sucks

+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + diff --git a/js/index.js b/js/index.js index 87d8479..d28b1cd 100644 --- a/js/index.js +++ b/js/index.js @@ -1,1086 +1,1086 @@ -//TODO FIND OUT WHY I HAVE TO RESIZE A TEXTBOX AND THEN START USING IT TO AVOID THE 1px WHITE LINE ON LEFT EDGES DURING IMG2IMG -//...lmao did setting min width 200 on info div fix that accidentally? once the canvas is infinite and the menu bar is hideable it'll probably be a problem again - -window.onload = startup; - -var stableDiffusionData = { - //includes img2img data but works for txt2img just fine - prompt: "", - negative_prompt: "", - seed: -1, - cfg_scale: null, - sampler_index: "DDIM", - steps: null, - denoising_strength: 1, - mask_blur: 0, - batch_size: null, - width: 512, - height: 512, - n_iter: null, // batch count - mask: "", - init_images: [], - inpaint_full_res: false, - inpainting_fill: 2, - enable_hr: false, - firstphase_width: 0, - firstphase_height: 0, - // here's some more fields that might be useful - - // ---txt2img specific: - // "enable_hr": false, // hires fix - // "denoising_strength": 0, // ok this is in both txt and img2img but txt2img only applies it if enable_hr == true - // "firstphase_width": 0, // hires fp w - // "firstphase_height": 0, // see above s/w/h/ - - // ---img2img specific - // "init_images": [ // imageS!??!? wtf maybe for batch img2img?? i just dump one base64 in here - // "string" - // ], - // "resize_mode": 0, - // "denoising_strength": 0.75, // yeah see - // "mask": "string", // string is just a base64 image - // "mask_blur": 4, - // "inpainting_fill": 0, // 0- fill, 1- orig, 2- latent noise, 3- latent nothing - // "inpaint_full_res": true, - // "inpaint_full_res_padding": 0, // px - // "inpainting_mask_invert": 0, // bool??????? wtf - // "include_init_images": false // ?????? -}; - -// stuff things use -var blockNewImages = false; -var returnedImages; -var imageIndex = 0; -var tmpImgXYWH = {}; -var host = ""; -var url = "/sdapi/v1/"; -var endpoint = "txt2img"; -var frameX = 512; -var frameY = 512; -var prevMouseX = 0; -var prevMouseY = 0; -var mouseX = 0; -var mouseY = 0; -var canvasX = 0; -var canvasY = 0; -var heldButton = 0; -var snapX = 0; -var snapY = 0; -var drawThis = {}; -const basePixelCount = 64; //64 px - ALWAYS 64 PX -var scaleFactor = 8; //x64 px -var snapToGrid = true; -var backupMaskPaintCanvas; //??? -var backupMaskPaintCtx; //...? look i am bad at this -var backupMaskChunk = null; -var backupMaskX = null; -var backupMaskY = null; -var totalImagesReturned; -var overMaskPx = 0; -var drawTargets = []; // is this needed? i only draw the last one anyway... -var dropTargets = []; // uhhh yeah similar to the above but for arbitrary dropped images -var arbitraryImage; -var arbitraryImageData; -var arbitraryImageBitmap; -var arbitraryImageBase64; // seriously js cmon work with me here -var placingArbitraryImage = false; // for when the user has loaded an existing image from their computer -var marchOffset = 0; -var stopMarching = null; -var inProgress = false; -var marchCoords = {}; - -// info div, sometimes hidden -let mouseXInfo = document.getElementById("mouseX"); -let mouseYInfo = document.getElementById("mouseY"); -let canvasXInfo = document.getElementById("canvasX"); -let canvasYInfo = document.getElementById("canvasY"); -let snapXInfo = document.getElementById("snapX"); -let snapYInfo = document.getElementById("snapY"); -let heldButtonInfo = document.getElementById("heldButton"); - -// canvases and related -const ovCanvas = document.getElementById("overlayCanvas"); // where mouse cursor renders -const ovCtx = ovCanvas.getContext("2d"); -const tgtCanvas = document.getElementById("targetCanvas"); // where "box" gets drawn before dream happens -const tgtCtx = tgtCanvas.getContext("2d"); -const maskPaintCanvas = document.getElementById("maskPaintCanvas"); // where masking brush gets painted -const maskPaintCtx = maskPaintCanvas.getContext("2d"); -const tempCanvas = document.getElementById("tempCanvas"); // where select/rejects get superimposed temporarily -const tempCtx = tempCanvas.getContext("2d"); -const imgCanvas = document.getElementById("canvas"); // where dreams go -const imgCtx = imgCanvas.getContext("2d"); -const bgCanvas = document.getElementById("backgroundCanvas"); // gray bg grid -const bgCtx = bgCanvas.getContext("2d"); - -function startup() { - testHostConfiguration(); - testHostConnection(); - - loadSettings(); - - const hostEl = document.getElementById("host"); - hostEl.onchange = () => { - host = hostEl.value.endsWith("/") - ? hostEl.value.substring(0, hostEl.value.length - 1) - : hostEl.value; - hostEl.value = host; - localStorage.setItem("host", host); - }; - - const promptEl = document.getElementById("prompt"); - promptEl.oninput = () => { - stableDiffusionData.prompt = promptEl.value; - promptEl.title = promptEl.value; - localStorage.setItem("prompt", stableDiffusionData.prompt); - }; - - const negPromptEl = document.getElementById("negPrompt"); - negPromptEl.oninput = () => { - stableDiffusionData.negative_prompt = negPromptEl.value; - negPromptEl.title = negPromptEl.value; - localStorage.setItem("neg_prompt", stableDiffusionData.negative_prompt); - }; - - drawBackground(); - changeSampler(); - changeMaskBlur(); - changeSeed(); - changeOverMaskPx(); - changeHiResFix(); - document.getElementById("overlayCanvas").onmousemove = mouseMove; - document.getElementById("overlayCanvas").onmousedown = mouseDown; - document.getElementById("overlayCanvas").onmouseup = mouseUp; - document.getElementById("scaleFactor").value = scaleFactor; -} - -/** - * Initial connection checks - */ -function testHostConfiguration() { - /** - * Check host configuration - */ - const hostEl = document.getElementById("host"); - - const requestHost = (prompt, def = "http://127.0.0.1:7860") => { - let value = window.prompt(prompt, def); - if (value === null) value = "http://127.0.0.1:7860"; - - value = value.endsWith("/") ? value.substring(0, value.length - 1) : value; - host = value; - hostEl.value = host; - localStorage.setItem("host", host); - - testHostConfiguration(); - }; - - const current = localStorage.getItem("host"); - if (current) { - if (!current.match(/^https?:\/\/[a-z0-9][a-z0-9.]+[a-z0-9](:[0-9]+)?$/i)) - requestHost( - "Host seems to be invalid! Please fix your host here:", - current - ); - } else { - requestHost( - "This seems to be the first time you are using openOutpaint! Please set your host here:" - ); - } -} - -function testHostConnection() { - function CheckInProgressError(message = "") { - this.name = "CheckInProgressError"; - this.message = message; - } - CheckInProgressError.prototype = Object.create(Error.prototype); - - const connectionIndicator = document.getElementById( - "connection-status-indicator" - ); - - let connectionStatus = false; - let firstTimeOnline = true; - - const setConnectionStatus = (status) => { - const statuses = { - online: () => { - connectionIndicator.classList.add("online"); - connectionIndicator.classList.remove( - "cors-issue", - "offline", - "server-error" - ); - connectionIndicator.title = "Connected"; - connectionStatus = true; - }, - error: () => { - connectionIndicator.classList.add("server-error"); - connectionIndicator.classList.remove("online", "offline", "cors-issue"); - connectionIndicator.title = - "Server is online, but is returning an error response"; - connectionStatus = false; - }, - corsissue: () => { - connectionIndicator.classList.add("cors-issue"); - connectionIndicator.classList.remove( - "online", - "offline", - "server-error" - ); - connectionIndicator.title = - "Server is online, but CORS is blocking our requests"; - connectionStatus = false; - }, - offline: () => { - connectionIndicator.classList.add("offline"); - connectionIndicator.classList.remove( - "cors-issue", - "online", - "server-error" - ); - connectionIndicator.title = - "Server seems to be offline. Please check the console for more information."; - connectionStatus = false; - }, - }; - - statuses[status] && statuses[status](); - }; - - let checkInProgress = false; - - const checkConnection = async (notify = false) => { - if (checkInProgress) - throw new CheckInProgressError( - "Check is currently in progress, please try again" - ); - checkInProgress = true; - var url = document.getElementById("host").value + "/startup-events"; - // Attempt normal request - try { - const response = await fetch(url); - - if (response.status === 200) { - setConnectionStatus("online"); - // Load data as soon as connection is first stablished - if (firstTimeOnline) { - getSamplers(); - getUpscalers(); - getModels(); - firstTimeOnline = false; - } - } else { - setConnectionStatus("error"); - const message = `Server responded with ${response.status} - ${response.statusText}. Try running the webui with the flag '--api'`; - console.error(message); - if (notify) alert(message); - } - } catch (e) { - try { - // Tests if problem is CORS - await fetch(url, {mode: "no-cors"}); - - setConnectionStatus("corsissue"); - const message = `CORS is blocking our requests. Try running the webui with the flag '--cors-allow-origins=${document.URL.substring( - 0, - document.URL.length - 1 - )}'`; - console.error(message); - if (notify) alert(message); - } catch (e) { - setConnectionStatus("offline"); - const message = `The server seems to be offline. Is host '${ - document.getElementById("host").value - }' correct?`; - console.error(message); - if (notify) alert(message); - } - } - checkInProgress = false; - return status; - }; - - checkConnection(true); - - // On click, attempt to refresh - connectionIndicator.onclick = async () => { - try { - await checkConnection(true); - checked = true; - } catch (e) { - console.debug("Already refreshing"); - } - }; - - // Checks every 5 seconds if offline, 30 seconds if online - const checkAgain = () => { - setTimeout( - () => { - checkConnection(); - checkAgain(); - }, - connectionStatus ? 30000 : 5000 - ); - }; - - checkAgain(); -} - -function dream( - x, - y, - prompt, - extra = { - method: endpoint, - stopMarching: () => {}, - bb: {x, y, w: prompt.width, h: prompt.height}, - } -) { - tmpImgXYWH.x = x; - tmpImgXYWH.y = y; - tmpImgXYWH.w = prompt.width; - tmpImgXYWH.h = prompt.height; - console.log( - "dreaming to " + - host + - url + - (extra.method || endpoint) + - ":\r\n" + - JSON.stringify(prompt) - ); - console.info(`dreaming "${prompt.prompt}"`); - console.debug(prompt); - - // Start checking for progress - const progressCheck = checkProgress(extra.bb); - postData(prompt, extra) - .then((data) => { - returnedImages = data.images; - totalImagesReturned = data.images.length; - blockNewImages = true; - //console.log(data); // JSON data parsed by `data.json()` call - imageAcceptReject(x, y, data, extra); - }) - .finally(() => clearInterval(progressCheck)); -} - -async function postData(promptData, extra = null) { - this.host = document.getElementById("host").value; - // Default options are marked with * - const response = await fetch( - this.host + this.url + extra.method || endpoint, - { - method: "POST", // *GET, POST, PUT, DELETE, etc. - mode: "cors", // no-cors, *cors, same-origin - cache: "default", // *default, no-cache, reload, force-cache, only-if-cached - credentials: "same-origin", // include, *same-origin, omit - headers: { - Accept: "application/json", - "Content-Type": "application/json", - }, - redirect: "follow", // manual, *follow, error - referrerPolicy: "no-referrer", // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url - body: JSON.stringify(promptData), // body data type must match "Content-Type" header - } - ); - return response.json(); // parses JSON response into native JavaScript objects -} - -function imageAcceptReject(x, y, data, extra = null) { - inProgress = false; - document.getElementById("progressDiv").remove(); - const img = new Image(); - img.onload = function () { - backupAndClearMask(x, y, img.width, img.height); - tempCtx.drawImage(img, x, y); //imgCtx for actual image, tmp for... holding? - var div = document.createElement("div"); - div.id = "veryTempDiv"; - div.style.position = "absolute"; - div.style.left = parseInt(x) < 0 ? 0 + "px" : parseInt(x) + "px"; - div.style.top = parseInt(y + data.parameters.height) + "px"; - div.style.width = "200px"; - div.style.height = "70px"; - div.innerHTML = - ' of '; - - document.getElementById("tempDiv").appendChild(div); - document.getElementById("currentImgIndex").innerText = "1"; - document.getElementById("totalImgIndex").innerText = totalImagesReturned; - }; - // set the image displayed as the first regardless of batch size/count - imageIndex = 0; - // load the image data after defining the closure - img.src = "data:image/png;base64," + returnedImages[imageIndex]; -} - -function accept(evt) { - // write image to imgcanvas - stopMarching && stopMarching(); - stopMarching = null; - clearBackupMask(); - placeImage(); - removeChoiceButtons(); - clearTargetMask(); - blockNewImages = false; -} - -function reject(evt) { - // remove image entirely - stopMarching && stopMarching(); - stopMarching = null; - restoreBackupMask(); - clearBackupMask(); - clearTargetMask(); - removeChoiceButtons(); - blockNewImages = false; -} - -function resource(evt) { - // send image to resources - const img = new Image(); - // load the image data after defining the closure - img.src = "data:image/png;base64," + returnedImages[imageIndex]; - - tools.stamp.state.addResource( - prompt("Enter new resource name", "Dream Resource"), - img - ); -} - -function newImage(evt) { - clearPaintedMask(); - clearBackupMask(); - clearTargetMask(); - commands.runCommand("eraseImage", "Clear Canvas", { - x: 0, - y: 0, - w: imgCanvas.width, - h: imgCanvas.height, - }); -} - -function prevImg(evt) { - if (imageIndex == 0) { - imageIndex = totalImagesReturned; - } - changeImg(false); -} - -function nextImg(evt) { - if (imageIndex == totalImagesReturned - 1) { - imageIndex = -1; - } - changeImg(true); -} - -function changeImg(forward) { - const img = new Image(); - tempCtx.clearRect(0, 0, tempCtx.width, tempCtx.height); - img.onload = function () { - tempCtx.drawImage(img, tmpImgXYWH.x, tmpImgXYWH.y); //imgCtx for actual image, tmp for... holding? - }; - var tmpIndex = document.getElementById("currentImgIndex"); - if (forward) { - imageIndex++; - } else { - imageIndex--; - } - tmpIndex.innerText = imageIndex + 1; - // load the image data after defining the closure - img.src = "data:image/png;base64," + returnedImages[imageIndex]; //TODO need way to dream batches and select from results -} - -function removeChoiceButtons(evt) { - const element = document.getElementById("veryTempDiv"); - element.remove(); - tempCtx.clearRect(0, 0, tempCanvas.width, tempCanvas.height); -} - -function backupAndClearMask(x, y, w, h) { - var clearArea = maskPaintCtx.createImageData(w, h); - backupMaskChunk = maskPaintCtx.getImageData(x, y, w, h); - backupMaskX = x; - backupMaskY = y; - var clearD = clearArea.data; - for (i = 0; i < clearD.length; i += 4) { - clearD[i] = 0; - clearD[i + 1] = 0; - clearD[i + 2] = 0; - clearD[i + 3] = 0; - } - maskPaintCtx.putImageData(clearArea, x, y); -} - -function restoreBackupMask() { - // reapply mask if exists - if (backupMaskChunk != null && backupMaskX != null && backupMaskY != null) { - // backup mask data exists - var iData = new ImageData( - backupMaskChunk.data, - backupMaskChunk.height, - backupMaskChunk.width - ); - maskPaintCtx.putImageData(iData, backupMaskX, backupMaskY); - } -} - -function clearBackupMask() { - // clear backupmask - backupMaskChunk = null; - backupMaskX = null; - backupMaskY = null; -} - -function clearTargetMask() { - tgtCtx.clearRect(0, 0, tgtCanvas.width, tgtCanvas.height); -} - -function clearImgMask() { - imgCtx.clearRect(0, 0, imgCanvas.width, imgCanvas.height); -} - -function clearPaintedMask() { - maskPaintCtx.clearRect(0, 0, maskPaintCanvas.width, maskPaintCanvas.height); -} - -function placeImage() { - const img = new Image(); - img.onload = function () { - commands.runCommand("drawImage", "Image Dream", { - x: tmpImgXYWH.x, - y: tmpImgXYWH.y, - image: img, - }); - tmpImgXYWH = {}; - returnedImages = null; - }; - // load the image data after defining the closure - img.src = "data:image/png;base64," + returnedImages[imageIndex]; -} - -function sleep(ms) { - // what was this even for, anyway? - return new Promise((resolve) => setTimeout(resolve, ms)); -} - -function march(bb) { - let offset = 0; - - const interval = setInterval(() => { - drawMarchingAnts(bb, offset++); - offset %= 16; - }, 20); - - return () => clearInterval(interval); -} - -function drawMarchingAnts(bb, offset) { - clearTargetMask(); - tgtCtx.strokeStyle = "#FFFFFFFF"; //"#55000077"; - tgtCtx.setLineDash([4, 2]); - tgtCtx.lineDashOffset = -offset; - tgtCtx.strokeRect(bb.x, bb.y, bb.w, bb.h); -} - -function checkProgress(bb) { - document.getElementById("progressDiv") && - document.getElementById("progressDiv").remove(); - // Skip image to stop using a ton of networking resources - endpoint = "progress?skip_current_image=true"; - var div = document.createElement("div"); - div.id = "progressDiv"; - div.style.position = "absolute"; - div.style.width = "200px"; - div.style.height = "70px"; - div.style.left = parseInt(bb.x + bb.w - 100) + "px"; - div.style.top = parseInt(bb.y + bb.h) + "px"; - div.innerHTML = ''; - document.getElementById("tempDiv").appendChild(div); - return setInterval(() => { - fetch(host + url + endpoint) - .then((response) => response.json()) - .then((data) => { - var estimate = - Math.round(data.progress * 100) + - "% :: " + - Math.floor(data.eta_relative) + - " sec."; - - document.getElementById("estRemaining").innerText = estimate; - }); - }, 1000); -} - -function mouseMove(evt) { - const rect = ovCanvas.getBoundingClientRect(); // not-quite pixel offset was driving me insane - const canvasOffsetX = rect.left; - const canvasOffsetY = rect.top; - heldButton = evt.buttons; - mouseXInfo.innerText = mouseX = evt.clientX; - mouseYInfo.innerText = mouseY = evt.clientY; - canvasXInfo.innerText = canvasX = parseInt(evt.clientX - rect.left); - canvasYInfo.innerText = canvasY = parseInt(evt.clientY - rect.top); - snapXInfo.innerText = canvasX + snap(canvasX); - snapYInfo.innerText = canvasY + snap(canvasY); - heldButtonInfo.innerText = heldButton; - ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); // clear out the previous mouse cursor - if (placingArbitraryImage) { - // ugh refactor so this isn't duplicated between arbitrary image and dream reticle modes - snapOffsetX = 0; - snapOffsetY = 0; - if (snapToGrid) { - snapOffsetX = snap(canvasX, false); - snapOffsetY = snap(canvasY, false); - } - finalX = snapOffsetX + canvasX; - finalY = snapOffsetY + canvasY; - ovCtx.drawImage(arbitraryImage, finalX, finalY); - } -} - -function mouseDown(evt) { - const rect = ovCanvas.getBoundingClientRect(); - var oddOffset = 0; - if (scaleFactor % 2 != 0) { - oddOffset = basePixelCount / 2; - } - if (evt.button == 0) { - // left click - if (placingArbitraryImage) { - var nextBox = {}; - nextBox.x = evt.offsetX; - nextBox.y = evt.offsetY; - nextBox.w = arbitraryImageData.width; - nextBox.h = arbitraryImageData.height; - dropTargets.push(nextBox); - } - } -} - -function mouseUp(evt) { - if (evt.button == 0) { - // left click - if (placingArbitraryImage) { - // jeez i REALLY need to refactor tons of this to not be duplicated all over, that's definitely my next chore after figuring out that razza frazza overmask fade - var target = dropTargets[dropTargets.length - 1]; //get the last one... why am i storing all of them? - snapOffsetX = 0; - snapOffsetY = 0; - if (snapToGrid) { - snapOffsetX = snap(target.x, false); - snapOffsetY = snap(target.y, false); - } - finalX = snapOffsetX + target.x; - finalY = snapOffsetY + target.y; - - drawThis.x = finalX; - drawThis.y = finalY; - drawThis.w = target.w; - drawThis.h = target.h; - drawIt = drawThis; // i still think this is really stupid and redundant and unnecessary and redundant - drop(drawIt); - } - } -} - -function changeSampler() { - if (!document.getElementById("samplerSelect").value == "") { - // must be done, since before getSamplers is done, the options are empty - console.log(document.getElementById("samplerSelect").value == ""); - stableDiffusionData.sampler_index = - document.getElementById("samplerSelect").value; - localStorage.setItem("sampler", stableDiffusionData.sampler_index); - } -} - -const makeSlider = ( - label, - el, - lsKey, - min, - max, - step, - defaultValue, - valuecb = null -) => { - const local = localStorage.getItem(lsKey); - const def = parseFloat(local === null ? defaultValue : local); - return createSlider(label, el, { - valuecb: - valuecb || - ((v) => { - stableDiffusionData[lsKey] = v; - localStorage.setItem(lsKey, v); - }), - min, - max, - step, - defaultValue: def, - }); -}; - -makeSlider( - "CFG Scale", - document.getElementById("cfgScale"), - "cfg_scale", - -1, - 25, - 0.5, - 7.0 -); -makeSlider( - "Batch Size", - document.getElementById("batchSize"), - "batch_size", - 1, - 8, - 1, - 2 -); -makeSlider( - "Iterations", - document.getElementById("batchCount"), - "n_iter", - 1, - 8, - 1, - 2 -); -makeSlider( - "Scale Factor", - document.getElementById("scaleFactor"), - "scale_factor", - 1, - 16, - 1, - 8, - (v) => { - scaleFactor = v; - } -); - -makeSlider("Steps", document.getElementById("steps"), "steps", 1, 70, 1, 30); - -function changeSnapMode() { - snapToGrid = document.getElementById("cbxSnap").checked; -} - -function changeMaskBlur() { - stableDiffusionData.mask_blur = document.getElementById("maskBlur").value; - localStorage.setItem("mask_blur", stableDiffusionData.mask_blur); -} - -function changeSeed() { - stableDiffusionData.seed = document.getElementById("seed").value; - localStorage.setItem("seed", stableDiffusionData.seed); -} - -function changeOverMaskPx() { - // overMaskPx = document.getElementById("overMaskPx").value; - // localStorage.setItem("overmask_px", overMaskPx); -} - -function changeHiResFix() { - stableDiffusionData.enable_hr = Boolean( - document.getElementById("cbxHRFix").checked - ); - localStorage.setItem("enable_hr", stableDiffusionData.enable_hr); -} - -function isCanvasBlank(x, y, w, h, specifiedCanvas) { - var canvas = document.getElementById(specifiedCanvas.id); - return !canvas - .getContext("2d") - .getImageData(x, y, w, h) - .data.some((channel) => channel !== 0); -} - -function drawBackground() { - // Checkerboard - let darkTileColor = "#333"; - let lightTileColor = "#555"; - for (var x = 0; x < bgCanvas.width; x += 64) { - for (var y = 0; y < bgCanvas.height; y += 64) { - bgCtx.fillStyle = (x + y) % 128 === 0 ? lightTileColor : darkTileColor; - bgCtx.fillRect(x, y, 64, 64); - } - } -} - -function getUpscalers() { - /* - so for some reason when upscalers request returns upscalers, the real-esrgan model names are incorrect, and need to be fetched from /sdapi/v1/realesrgan-models - also the realesrgan models returned are not all correct, extra fun! - LDSR seems to have problems so we dont add that either -> RuntimeError: Number of dimensions of repeat dims can not be smaller than number of dimensions of tensor - need to figure out why that is, if you dont get this error then you can add it back in - - Hacky way to get the correct list all in one go is to purposefully make an incorrect request, which then returns - { detail: "Invalid upscaler, needs to be on of these: None , Lanczos , Nearest , LDSR , BSRGAN , R-ESRGAN General 4xV3 , R-ESRGAN 4x+ Anime6B , ScuNET , ScuNET PSNR , SwinIR_4x" } - from which we can extract the correct list of upscalers - */ - - // hacky way to get the correct list of upscalers - var upscalerSelect = document.getElementById("upscalers"); - var extras_url = - document.getElementById("host").value + "/sdapi/v1/extra-single-image/"; // endpoint for upscaling, needed for the hacky way to get the correct list of upscalers - var empty_image = new Image(512, 512); - empty_image.src = - "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAFCAAAAABCAYAAAChpRsuAAAALklEQVR42u3BAQ0AAAgDoJvc6LeHAybtBgAAAAAAAAAAAAAAAAAAAAAAAAB47QD2wAJ/LnnqGgAAAABJRU5ErkJggg=="; //transparent pixel - var purposefully_incorrect_data = { - "resize-mode": 0, // 0 = just resize, 1 = crop and resize, 2 = resize and fill i assume based on theimg2img tabs options - upscaling_resize: 2, - upscaler_1: "fake_upscaler", - image: empty_image.src, - }; - - fetch(extras_url, { - method: "POST", - headers: { - Accept: "application/json", - "Content-Type": "application/json", - }, - body: JSON.stringify(purposefully_incorrect_data), - }) - .then((response) => response.json()) - .then((data) => { - console.log("purposefully_incorrect_data response, ignore above error"); - // result = purposefully_incorrect_data response: Invalid upscaler, needs to be on of these: None , Lanczos , Nearest , LDSR , BSRGAN , R-ESRGAN General 4xV3 , R-ESRGAN 4x+ Anime6B , ScuNET , ScuNET PSNR , SwinIR_4x - let upscalers = data.detail.split(": ")[1].trim().split(" , "); // converting the result to a list of upscalers - for (var i = 0; i < upscalers.length; i++) { - // if(upscalers[i] == "LDSR") continue; // Skip LDSR, see reason in the first comment // readded because worksonmymachine.jpg but leaving it here in case of, uh, future disaster? - var option = document.createElement("option"); - option.text = upscalers[i]; - option.value = upscalers[i]; - upscalerSelect.add(option); - } - }); - - /* THE NON HACKY WAY THAT I SIMPLY COULD NOT GET TO PRODUCE A LIST WITHOUT NON WORKING UPSCALERS, FEEL FREE TO TRY AND FIGURE IT OUT - - var url = document.getElementById("host").value + "/sdapi/v1/upscalers"; - var realesrgan_url = document.getElementById("host").value + "/sdapi/v1/realesrgan-models"; - - // get upscalers - fetch(url) - .then((response) => response.json()) - .then((data) => { - console.log(data); - - for (var i = 0; i < data.length; i++) { - var option = document.createElement("option"); - - if (data[i].name.includes("ESRGAN") || data[i].name.includes("LDSR")) { - continue; - } - option.text = data[i].name; - upscalerSelect.add(option); - } - }) - .catch((error) => { - alert( - "Error getting upscalers, please check console for additional info\n" + - error - ); - }); - // fetch realesrgan models separately - fetch(realesrgan_url) - .then((response) => response.json()) - .then((data) => { - var model = data; - for(var i = 0; i < model.length; i++){ - let option = document.createElement("option"); - option.text = model[i].name; - option.value = model[i].name; - upscalerSelect.add(option); - - } - - }) - */ -} - -async function getModels() { - var modelSelect = document.getElementById("models"); - var url = document.getElementById("host").value + "/sdapi/v1/sd-models"; - await fetch(url) - .then((response) => response.json()) - .then((data) => { - //console.log(data); All models - for (var i = 0; i < data.length; i++) { - var option = document.createElement("option"); - option.text = data[i].model_name; - option.value = data[i].title; - modelSelect.add(option); - } - }); - - // get currently loaded model - - await fetch(document.getElementById("host").value + "/sdapi/v1/options") - .then((response) => response.json()) - .then((data) => { - var model = data.sd_model_checkpoint; - console.log("Current model: " + model); - modelSelect.value = model; - }); -} - -function changeModel() { - // change the model - console.log("changing model to " + document.getElementById("models").value); - var model_title = document.getElementById("models").value; - var payload = { - sd_model_checkpoint: model_title, - }; - var url = document.getElementById("host").value + "/sdapi/v1/options/"; - fetch(url, { - method: "POST", - mode: "cors", // no-cors, *cors, same-origin - cache: "default", // *default, no-cache, reload, force-cache, only-if-cached - credentials: "same-origin", // include, *same-origin, omit - redirect: "follow", // manual, *follow, error - referrerPolicy: "no-referrer", // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url - headers: { - Accept: "application/json", - "Content-Type": "application/json", - }, - body: JSON.stringify(payload), - }) - .then((response) => response.json()) - .then(() => { - alert("Model changed to " + model_title); - }) - .catch((error) => { - alert( - "Error changing model, please check console for additional info\n" + - error - ); - }); -} - -function getSamplers() { - var samplerSelect = document.getElementById("samplerSelect"); - var url = document.getElementById("host").value + "/sdapi/v1/samplers"; - fetch(url) - .then((response) => response.json()) - .then((data) => { - //console.log(data); All samplers - for (var i = 0; i < data.length; i++) { - // PLMS SAMPLER DOES NOT WORK FOR ANY IMAGES BEYOND FOR THE INITIAL IMAGE (for me at least), GIVES ASGI Exception; AttributeError: 'PLMSSampler' object has no attribute 'stochastic_encode' - - var option = document.createElement("option"); - option.text = data[i].name; - option.value = data[i].name; - samplerSelect.add(option); - } - if (localStorage.getItem("sampler") != null) { - samplerSelect.value = localStorage.getItem("sampler"); - } else { - // needed now, as hardcoded sampler cant be guaranteed to be in the list - samplerSelect.value = data[0].name; - localStorage.setItem("sampler", samplerSelect.value); - } - }) - .catch((error) => { - alert( - "Error getting samplers, please check console for additional info\n" + - error - ); - }); -} -async function upscaleAndDownload() { - // Future improvements: some upscalers take a while to upscale, so we should show a loading bar or something, also a slider for the upscale amount - - // get cropped canvas, send it to upscaler, download result - var upscale_factor = 2; // TODO: make this a user input 1.x - 4.0 or something - var upscaler = document.getElementById("upscalers").value; - var croppedCanvas = cropCanvas(imgCanvas); - if (croppedCanvas != null) { - var upscaler = document.getElementById("upscalers").value; - var url = - document.getElementById("host").value + "/sdapi/v1/extra-single-image/"; - var imgdata = croppedCanvas.toDataURL("image/png"); - var data = { - "resize-mode": 0, // 0 = just resize, 1 = crop and resize, 2 = resize and fill i assume based on theimg2img tabs options - upscaling_resize: upscale_factor, - upscaler_1: upscaler, - image: imgdata, - }; - console.log(data); - await fetch(url, { - method: "POST", - headers: { - Accept: "application/json", - "Content-Type": "application/json", - }, - body: JSON.stringify(data), - }) - .then((response) => response.json()) - .then((data) => { - console.log(data); - var link = document.createElement("a"); - link.download = - new Date() - .toISOString() - .slice(0, 19) - .replace("T", " ") - .replace(":", " ") + - " openOutpaint image upscaler_" + - upscaler + - ".png"; - link.href = "data:image/png;base64," + data["image"]; - link.click(); - }); - } -} - -function loadSettings() { - // set default values if not set - var _prompt = - localStorage.getItem("prompt") == null - ? "ocean floor scientific expedition, underwater wildlife" - : localStorage.getItem("prompt"); - var _negprompt = - localStorage.getItem("neg_prompt") == null - ? "people, person, humans, human, divers, diver, glitch, error, text, watermark, bad quality, blurry" - : localStorage.getItem("neg_prompt"); - var _sampler = - localStorage.getItem("sampler") == null - ? "DDIM" - : localStorage.getItem("sampler"); - var _mask_blur = - localStorage.getItem("mask_blur") == null - ? 0 - : localStorage.getItem("mask_blur"); - var _seed = - localStorage.getItem("seed") == null ? -1 : localStorage.getItem("seed"); - var _enable_hr = Boolean( - localStorage.getItem("enable_hr") == (null || "false") - ? false - : localStorage.getItem("enable_hr") - ); - var _enable_erase = Boolean( - localStorage.getItem("enable_erase") == (null || "false") - ? false - : localStorage.getItem("enable_erase") - ); - var _overmask_px = - localStorage.getItem("overmask_px") == null - ? 0 - : localStorage.getItem("overmask_px"); - - // set the values into the UI - document.getElementById("prompt").value = String(_prompt); - document.getElementById("prompt").title = String(_prompt); - document.getElementById("negPrompt").value = String(_negprompt); - document.getElementById("negPrompt").title = String(_negprompt); - document.getElementById("samplerSelect").value = String(_sampler); - document.getElementById("maskBlur").value = Number(_mask_blur); - document.getElementById("seed").value = Number(_seed); - document.getElementById("cbxHRFix").checked = Boolean(_enable_hr); - // document.getElementById("overMaskPx").value = Number(_overmask_px); -} - -document.getElementById("mainHSplit").addEventListener("wheel", (evn) => { - evn.preventDefault(); -}); +//TODO FIND OUT WHY I HAVE TO RESIZE A TEXTBOX AND THEN START USING IT TO AVOID THE 1px WHITE LINE ON LEFT EDGES DURING IMG2IMG +//...lmao did setting min width 200 on info div fix that accidentally? once the canvas is infinite and the menu bar is hideable it'll probably be a problem again + +window.onload = startup; + +var stableDiffusionData = { + //includes img2img data but works for txt2img just fine + prompt: "", + negative_prompt: "", + seed: -1, + cfg_scale: null, + sampler_index: "DDIM", + steps: null, + denoising_strength: 1, + mask_blur: 0, + batch_size: null, + width: 512, + height: 512, + n_iter: null, // batch count + mask: "", + init_images: [], + inpaint_full_res: false, + inpainting_fill: 2, + enable_hr: false, + firstphase_width: 0, + firstphase_height: 0, + // here's some more fields that might be useful + + // ---txt2img specific: + // "enable_hr": false, // hires fix + // "denoising_strength": 0, // ok this is in both txt and img2img but txt2img only applies it if enable_hr == true + // "firstphase_width": 0, // hires fp w + // "firstphase_height": 0, // see above s/w/h/ + + // ---img2img specific + // "init_images": [ // imageS!??!? wtf maybe for batch img2img?? i just dump one base64 in here + // "string" + // ], + // "resize_mode": 0, + // "denoising_strength": 0.75, // yeah see + // "mask": "string", // string is just a base64 image + // "mask_blur": 4, + // "inpainting_fill": 0, // 0- fill, 1- orig, 2- latent noise, 3- latent nothing + // "inpaint_full_res": true, + // "inpaint_full_res_padding": 0, // px + // "inpainting_mask_invert": 0, // bool??????? wtf + // "include_init_images": false // ?????? +}; + +// stuff things use +var blockNewImages = false; +var returnedImages; +var imageIndex = 0; +var tmpImgXYWH = {}; +var host = ""; +var url = "/sdapi/v1/"; +var endpoint = "txt2img"; +var frameX = 512; +var frameY = 512; +var prevMouseX = 0; +var prevMouseY = 0; +var mouseX = 0; +var mouseY = 0; +var canvasX = 0; +var canvasY = 0; +var heldButton = 0; +var snapX = 0; +var snapY = 0; +var drawThis = {}; +const basePixelCount = 64; //64 px - ALWAYS 64 PX +var scaleFactor = 8; //x64 px +var snapToGrid = true; +var backupMaskPaintCanvas; //??? +var backupMaskPaintCtx; //...? look i am bad at this +var backupMaskChunk = null; +var backupMaskX = null; +var backupMaskY = null; +var totalImagesReturned; +var overMaskPx = 0; +var drawTargets = []; // is this needed? i only draw the last one anyway... +var dropTargets = []; // uhhh yeah similar to the above but for arbitrary dropped images +var arbitraryImage; +var arbitraryImageData; +var arbitraryImageBitmap; +var arbitraryImageBase64; // seriously js cmon work with me here +var placingArbitraryImage = false; // for when the user has loaded an existing image from their computer +var marchOffset = 0; +var stopMarching = null; +var inProgress = false; +var marchCoords = {}; + +// info div, sometimes hidden +let mouseXInfo = document.getElementById("mouseX"); +let mouseYInfo = document.getElementById("mouseY"); +let canvasXInfo = document.getElementById("canvasX"); +let canvasYInfo = document.getElementById("canvasY"); +let snapXInfo = document.getElementById("snapX"); +let snapYInfo = document.getElementById("snapY"); +let heldButtonInfo = document.getElementById("heldButton"); + +// canvases and related +const ovCanvas = document.getElementById("overlayCanvas"); // where mouse cursor renders +const ovCtx = ovCanvas.getContext("2d"); +const tgtCanvas = document.getElementById("targetCanvas"); // where "box" gets drawn before dream happens +const tgtCtx = tgtCanvas.getContext("2d"); +const maskPaintCanvas = document.getElementById("maskPaintCanvas"); // where masking brush gets painted +const maskPaintCtx = maskPaintCanvas.getContext("2d"); +const tempCanvas = document.getElementById("tempCanvas"); // where select/rejects get superimposed temporarily +const tempCtx = tempCanvas.getContext("2d"); +const imgCanvas = document.getElementById("canvas"); // where dreams go +const imgCtx = imgCanvas.getContext("2d"); +const bgCanvas = document.getElementById("backgroundCanvas"); // gray bg grid +const bgCtx = bgCanvas.getContext("2d"); + +function startup() { + testHostConfiguration(); + testHostConnection(); + + loadSettings(); + + const hostEl = document.getElementById("host"); + hostEl.onchange = () => { + host = hostEl.value.endsWith("/") + ? hostEl.value.substring(0, hostEl.value.length - 1) + : hostEl.value; + hostEl.value = host; + localStorage.setItem("host", host); + }; + + const promptEl = document.getElementById("prompt"); + promptEl.oninput = () => { + stableDiffusionData.prompt = promptEl.value; + promptEl.title = promptEl.value; + localStorage.setItem("prompt", stableDiffusionData.prompt); + }; + + const negPromptEl = document.getElementById("negPrompt"); + negPromptEl.oninput = () => { + stableDiffusionData.negative_prompt = negPromptEl.value; + negPromptEl.title = negPromptEl.value; + localStorage.setItem("neg_prompt", stableDiffusionData.negative_prompt); + }; + + drawBackground(); + changeSampler(); + changeMaskBlur(); + changeSeed(); + changeOverMaskPx(); + changeHiResFix(); + document.getElementById("overlayCanvas").onmousemove = mouseMove; + document.getElementById("overlayCanvas").onmousedown = mouseDown; + document.getElementById("overlayCanvas").onmouseup = mouseUp; + document.getElementById("scaleFactor").value = scaleFactor; +} + +/** + * Initial connection checks + */ +function testHostConfiguration() { + /** + * Check host configuration + */ + const hostEl = document.getElementById("host"); + + const requestHost = (prompt, def = "http://127.0.0.1:7860") => { + let value = window.prompt(prompt, def); + if (value === null) value = "http://127.0.0.1:7860"; + + value = value.endsWith("/") ? value.substring(0, value.length - 1) : value; + host = value; + hostEl.value = host; + localStorage.setItem("host", host); + + testHostConfiguration(); + }; + + const current = localStorage.getItem("host"); + if (current) { + if (!current.match(/^https?:\/\/[a-z0-9][a-z0-9.]+[a-z0-9](:[0-9]+)?$/i)) + requestHost( + "Host seems to be invalid! Please fix your host here:", + current + ); + } else { + requestHost( + "This seems to be the first time you are using openOutpaint! Please set your host here:" + ); + } +} + +function testHostConnection() { + function CheckInProgressError(message = "") { + this.name = "CheckInProgressError"; + this.message = message; + } + CheckInProgressError.prototype = Object.create(Error.prototype); + + const connectionIndicator = document.getElementById( + "connection-status-indicator" + ); + + let connectionStatus = false; + let firstTimeOnline = true; + + const setConnectionStatus = (status) => { + const statuses = { + online: () => { + connectionIndicator.classList.add("online"); + connectionIndicator.classList.remove( + "cors-issue", + "offline", + "server-error" + ); + connectionIndicator.title = "Connected"; + connectionStatus = true; + }, + error: () => { + connectionIndicator.classList.add("server-error"); + connectionIndicator.classList.remove("online", "offline", "cors-issue"); + connectionIndicator.title = + "Server is online, but is returning an error response"; + connectionStatus = false; + }, + corsissue: () => { + connectionIndicator.classList.add("cors-issue"); + connectionIndicator.classList.remove( + "online", + "offline", + "server-error" + ); + connectionIndicator.title = + "Server is online, but CORS is blocking our requests"; + connectionStatus = false; + }, + offline: () => { + connectionIndicator.classList.add("offline"); + connectionIndicator.classList.remove( + "cors-issue", + "online", + "server-error" + ); + connectionIndicator.title = + "Server seems to be offline. Please check the console for more information."; + connectionStatus = false; + }, + }; + + statuses[status] && statuses[status](); + }; + + let checkInProgress = false; + + const checkConnection = async (notify = false) => { + if (checkInProgress) + throw new CheckInProgressError( + "Check is currently in progress, please try again" + ); + checkInProgress = true; + var url = document.getElementById("host").value + "/startup-events"; + // Attempt normal request + try { + const response = await fetch(url); + + if (response.status === 200) { + setConnectionStatus("online"); + // Load data as soon as connection is first stablished + if (firstTimeOnline) { + getSamplers(); + getUpscalers(); + getModels(); + firstTimeOnline = false; + } + } else { + setConnectionStatus("error"); + const message = `Server responded with ${response.status} - ${response.statusText}. Try running the webui with the flag '--api'`; + console.error(message); + if (notify) alert(message); + } + } catch (e) { + try { + // Tests if problem is CORS + await fetch(url, {mode: "no-cors"}); + + setConnectionStatus("corsissue"); + const message = `CORS is blocking our requests. Try running the webui with the flag '--cors-allow-origins=${document.URL.substring( + 0, + document.URL.length - 1 + )}'`; + console.error(message); + if (notify) alert(message); + } catch (e) { + setConnectionStatus("offline"); + const message = `The server seems to be offline. Is host '${ + document.getElementById("host").value + }' correct?`; + console.error(message); + if (notify) alert(message); + } + } + checkInProgress = false; + return status; + }; + + checkConnection(true); + + // On click, attempt to refresh + connectionIndicator.onclick = async () => { + try { + await checkConnection(true); + checked = true; + } catch (e) { + console.debug("Already refreshing"); + } + }; + + // Checks every 5 seconds if offline, 30 seconds if online + const checkAgain = () => { + setTimeout( + () => { + checkConnection(); + checkAgain(); + }, + connectionStatus ? 30000 : 5000 + ); + }; + + checkAgain(); +} + +function dream( + x, + y, + prompt, + extra = { + method: endpoint, + stopMarching: () => {}, + bb: {x, y, w: prompt.width, h: prompt.height}, + } +) { + tmpImgXYWH.x = x; + tmpImgXYWH.y = y; + tmpImgXYWH.w = prompt.width; + tmpImgXYWH.h = prompt.height; + console.log( + "dreaming to " + + host + + url + + (extra.method || endpoint) + + ":\r\n" + + JSON.stringify(prompt) + ); + console.info(`dreaming "${prompt.prompt}"`); + console.debug(prompt); + + // Start checking for progress + const progressCheck = checkProgress(extra.bb); + postData(prompt, extra) + .then((data) => { + returnedImages = data.images; + totalImagesReturned = data.images.length; + blockNewImages = true; + //console.log(data); // JSON data parsed by `data.json()` call + imageAcceptReject(x, y, data, extra); + }) + .finally(() => clearInterval(progressCheck)); +} + +async function postData(promptData, extra = null) { + this.host = document.getElementById("host").value; + // Default options are marked with * + const response = await fetch( + this.host + this.url + extra.method || endpoint, + { + method: "POST", // *GET, POST, PUT, DELETE, etc. + mode: "cors", // no-cors, *cors, same-origin + cache: "default", // *default, no-cache, reload, force-cache, only-if-cached + credentials: "same-origin", // include, *same-origin, omit + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + redirect: "follow", // manual, *follow, error + referrerPolicy: "no-referrer", // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url + body: JSON.stringify(promptData), // body data type must match "Content-Type" header + } + ); + return response.json(); // parses JSON response into native JavaScript objects +} + +function imageAcceptReject(x, y, data, extra = null) { + inProgress = false; + document.getElementById("progressDiv").remove(); + const img = new Image(); + img.onload = function () { + backupAndClearMask(x, y, img.width, img.height); + tempCtx.drawImage(img, x, y); //imgCtx for actual image, tmp for... holding? + var div = document.createElement("div"); + div.id = "veryTempDiv"; + div.style.position = "absolute"; + div.style.left = parseInt(x) < 0 ? 0 + "px" : parseInt(x) + "px"; + div.style.top = parseInt(y + data.parameters.height) + "px"; + div.style.width = "200px"; + div.style.height = "70px"; + div.innerHTML = + ' of '; + + document.getElementById("tempDiv").appendChild(div); + document.getElementById("currentImgIndex").innerText = "1"; + document.getElementById("totalImgIndex").innerText = totalImagesReturned; + }; + // set the image displayed as the first regardless of batch size/count + imageIndex = 0; + // load the image data after defining the closure + img.src = "data:image/png;base64," + returnedImages[imageIndex]; +} + +function accept(evt) { + // write image to imgcanvas + stopMarching && stopMarching(); + stopMarching = null; + clearBackupMask(); + placeImage(); + removeChoiceButtons(); + clearTargetMask(); + blockNewImages = false; +} + +function reject(evt) { + // remove image entirely + stopMarching && stopMarching(); + stopMarching = null; + restoreBackupMask(); + clearBackupMask(); + clearTargetMask(); + removeChoiceButtons(); + blockNewImages = false; +} + +function resource(evt) { + // send image to resources + const img = new Image(); + // load the image data after defining the closure + img.src = "data:image/png;base64," + returnedImages[imageIndex]; + + tools.stamp.state.addResource( + prompt("Enter new resource name", "Dream Resource"), + img + ); +} + +function newImage(evt) { + clearPaintedMask(); + clearBackupMask(); + clearTargetMask(); + commands.runCommand("eraseImage", "Clear Canvas", { + x: 0, + y: 0, + w: imgCanvas.width, + h: imgCanvas.height, + }); +} + +function prevImg(evt) { + if (imageIndex == 0) { + imageIndex = totalImagesReturned; + } + changeImg(false); +} + +function nextImg(evt) { + if (imageIndex == totalImagesReturned - 1) { + imageIndex = -1; + } + changeImg(true); +} + +function changeImg(forward) { + const img = new Image(); + tempCtx.clearRect(0, 0, tempCtx.width, tempCtx.height); + img.onload = function () { + tempCtx.drawImage(img, tmpImgXYWH.x, tmpImgXYWH.y); //imgCtx for actual image, tmp for... holding? + }; + var tmpIndex = document.getElementById("currentImgIndex"); + if (forward) { + imageIndex++; + } else { + imageIndex--; + } + tmpIndex.innerText = imageIndex + 1; + // load the image data after defining the closure + img.src = "data:image/png;base64," + returnedImages[imageIndex]; //TODO need way to dream batches and select from results +} + +function removeChoiceButtons(evt) { + const element = document.getElementById("veryTempDiv"); + element.remove(); + tempCtx.clearRect(0, 0, tempCanvas.width, tempCanvas.height); +} + +function backupAndClearMask(x, y, w, h) { + var clearArea = maskPaintCtx.createImageData(w, h); + backupMaskChunk = maskPaintCtx.getImageData(x, y, w, h); + backupMaskX = x; + backupMaskY = y; + var clearD = clearArea.data; + for (i = 0; i < clearD.length; i += 4) { + clearD[i] = 0; + clearD[i + 1] = 0; + clearD[i + 2] = 0; + clearD[i + 3] = 0; + } + maskPaintCtx.putImageData(clearArea, x, y); +} + +function restoreBackupMask() { + // reapply mask if exists + if (backupMaskChunk != null && backupMaskX != null && backupMaskY != null) { + // backup mask data exists + var iData = new ImageData( + backupMaskChunk.data, + backupMaskChunk.height, + backupMaskChunk.width + ); + maskPaintCtx.putImageData(iData, backupMaskX, backupMaskY); + } +} + +function clearBackupMask() { + // clear backupmask + backupMaskChunk = null; + backupMaskX = null; + backupMaskY = null; +} + +function clearTargetMask() { + tgtCtx.clearRect(0, 0, tgtCanvas.width, tgtCanvas.height); +} + +function clearImgMask() { + imgCtx.clearRect(0, 0, imgCanvas.width, imgCanvas.height); +} + +function clearPaintedMask() { + maskPaintCtx.clearRect(0, 0, maskPaintCanvas.width, maskPaintCanvas.height); +} + +function placeImage() { + const img = new Image(); + img.onload = function () { + commands.runCommand("drawImage", "Image Dream", { + x: tmpImgXYWH.x, + y: tmpImgXYWH.y, + image: img, + }); + tmpImgXYWH = {}; + returnedImages = null; + }; + // load the image data after defining the closure + img.src = "data:image/png;base64," + returnedImages[imageIndex]; +} + +function sleep(ms) { + // what was this even for, anyway? + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +function march(bb) { + let offset = 0; + + const interval = setInterval(() => { + drawMarchingAnts(bb, offset++); + offset %= 16; + }, 20); + + return () => clearInterval(interval); +} + +function drawMarchingAnts(bb, offset) { + clearTargetMask(); + tgtCtx.strokeStyle = "#FFFFFFFF"; //"#55000077"; + tgtCtx.setLineDash([4, 2]); + tgtCtx.lineDashOffset = -offset; + tgtCtx.strokeRect(bb.x, bb.y, bb.w, bb.h); +} + +function checkProgress(bb) { + document.getElementById("progressDiv") && + document.getElementById("progressDiv").remove(); + // Skip image to stop using a ton of networking resources + endpoint = "progress?skip_current_image=true"; + var div = document.createElement("div"); + div.id = "progressDiv"; + div.style.position = "absolute"; + div.style.width = "200px"; + div.style.height = "70px"; + div.style.left = parseInt(bb.x + bb.w - 100) + "px"; + div.style.top = parseInt(bb.y + bb.h) + "px"; + div.innerHTML = ''; + document.getElementById("tempDiv").appendChild(div); + return setInterval(() => { + fetch(host + url + endpoint) + .then((response) => response.json()) + .then((data) => { + var estimate = + Math.round(data.progress * 100) + + "% :: " + + Math.floor(data.eta_relative) + + " sec."; + + document.getElementById("estRemaining").innerText = estimate; + }); + }, 1000); +} + +function mouseMove(evt) { + const rect = ovCanvas.getBoundingClientRect(); // not-quite pixel offset was driving me insane + const canvasOffsetX = rect.left; + const canvasOffsetY = rect.top; + heldButton = evt.buttons; + mouseXInfo.innerText = mouseX = evt.clientX; + mouseYInfo.innerText = mouseY = evt.clientY; + canvasXInfo.innerText = canvasX = parseInt(evt.clientX - rect.left); + canvasYInfo.innerText = canvasY = parseInt(evt.clientY - rect.top); + snapXInfo.innerText = canvasX + snap(canvasX); + snapYInfo.innerText = canvasY + snap(canvasY); + heldButtonInfo.innerText = heldButton; + ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); // clear out the previous mouse cursor + if (placingArbitraryImage) { + // ugh refactor so this isn't duplicated between arbitrary image and dream reticle modes + snapOffsetX = 0; + snapOffsetY = 0; + if (snapToGrid) { + snapOffsetX = snap(canvasX, false); + snapOffsetY = snap(canvasY, false); + } + finalX = snapOffsetX + canvasX; + finalY = snapOffsetY + canvasY; + ovCtx.drawImage(arbitraryImage, finalX, finalY); + } +} + +function mouseDown(evt) { + const rect = ovCanvas.getBoundingClientRect(); + var oddOffset = 0; + if (scaleFactor % 2 != 0) { + oddOffset = basePixelCount / 2; + } + if (evt.button == 0) { + // left click + if (placingArbitraryImage) { + var nextBox = {}; + nextBox.x = evt.offsetX; + nextBox.y = evt.offsetY; + nextBox.w = arbitraryImageData.width; + nextBox.h = arbitraryImageData.height; + dropTargets.push(nextBox); + } + } +} + +function mouseUp(evt) { + if (evt.button == 0) { + // left click + if (placingArbitraryImage) { + // jeez i REALLY need to refactor tons of this to not be duplicated all over, that's definitely my next chore after figuring out that razza frazza overmask fade + var target = dropTargets[dropTargets.length - 1]; //get the last one... why am i storing all of them? + snapOffsetX = 0; + snapOffsetY = 0; + if (snapToGrid) { + snapOffsetX = snap(target.x, false); + snapOffsetY = snap(target.y, false); + } + finalX = snapOffsetX + target.x; + finalY = snapOffsetY + target.y; + + drawThis.x = finalX; + drawThis.y = finalY; + drawThis.w = target.w; + drawThis.h = target.h; + drawIt = drawThis; // i still think this is really stupid and redundant and unnecessary and redundant + drop(drawIt); + } + } +} + +function changeSampler() { + if (!document.getElementById("samplerSelect").value == "") { + // must be done, since before getSamplers is done, the options are empty + console.log(document.getElementById("samplerSelect").value == ""); + stableDiffusionData.sampler_index = + document.getElementById("samplerSelect").value; + localStorage.setItem("sampler", stableDiffusionData.sampler_index); + } +} + +const makeSlider = ( + label, + el, + lsKey, + min, + max, + step, + defaultValue, + valuecb = null +) => { + const local = localStorage.getItem(lsKey); + const def = parseFloat(local === null ? defaultValue : local); + return createSlider(label, el, { + valuecb: + valuecb || + ((v) => { + stableDiffusionData[lsKey] = v; + localStorage.setItem(lsKey, v); + }), + min, + max, + step, + defaultValue: def, + }); +}; + +makeSlider( + "CFG Scale", + document.getElementById("cfgScale"), + "cfg_scale", + -1, + 25, + 0.5, + 7.0 +); +makeSlider( + "Batch Size", + document.getElementById("batchSize"), + "batch_size", + 1, + 8, + 1, + 2 +); +makeSlider( + "Iterations", + document.getElementById("batchCount"), + "n_iter", + 1, + 8, + 1, + 2 +); +makeSlider( + "Scale Factor", + document.getElementById("scaleFactor"), + "scale_factor", + 1, + 16, + 1, + 8, + (v) => { + scaleFactor = v; + } +); + +makeSlider("Steps", document.getElementById("steps"), "steps", 1, 70, 1, 30); + +function changeSnapMode() { + snapToGrid = document.getElementById("cbxSnap").checked; +} + +function changeMaskBlur() { + stableDiffusionData.mask_blur = document.getElementById("maskBlur").value; + localStorage.setItem("mask_blur", stableDiffusionData.mask_blur); +} + +function changeSeed() { + stableDiffusionData.seed = document.getElementById("seed").value; + localStorage.setItem("seed", stableDiffusionData.seed); +} + +function changeOverMaskPx() { + // overMaskPx = document.getElementById("overMaskPx").value; + // localStorage.setItem("overmask_px", overMaskPx); +} + +function changeHiResFix() { + stableDiffusionData.enable_hr = Boolean( + document.getElementById("cbxHRFix").checked + ); + localStorage.setItem("enable_hr", stableDiffusionData.enable_hr); +} + +function isCanvasBlank(x, y, w, h, specifiedCanvas) { + var canvas = document.getElementById(specifiedCanvas.id); + return !canvas + .getContext("2d") + .getImageData(x, y, w, h) + .data.some((channel) => channel !== 0); +} + +function drawBackground() { + // Checkerboard + let darkTileColor = "#333"; + let lightTileColor = "#555"; + for (var x = 0; x < bgCanvas.width; x += 64) { + for (var y = 0; y < bgCanvas.height; y += 64) { + bgCtx.fillStyle = (x + y) % 128 === 0 ? lightTileColor : darkTileColor; + bgCtx.fillRect(x, y, 64, 64); + } + } +} + +function getUpscalers() { + /* + so for some reason when upscalers request returns upscalers, the real-esrgan model names are incorrect, and need to be fetched from /sdapi/v1/realesrgan-models + also the realesrgan models returned are not all correct, extra fun! + LDSR seems to have problems so we dont add that either -> RuntimeError: Number of dimensions of repeat dims can not be smaller than number of dimensions of tensor + need to figure out why that is, if you dont get this error then you can add it back in + + Hacky way to get the correct list all in one go is to purposefully make an incorrect request, which then returns + { detail: "Invalid upscaler, needs to be on of these: None , Lanczos , Nearest , LDSR , BSRGAN , R-ESRGAN General 4xV3 , R-ESRGAN 4x+ Anime6B , ScuNET , ScuNET PSNR , SwinIR_4x" } + from which we can extract the correct list of upscalers + */ + + // hacky way to get the correct list of upscalers + var upscalerSelect = document.getElementById("upscalers"); + var extras_url = + document.getElementById("host").value + "/sdapi/v1/extra-single-image/"; // endpoint for upscaling, needed for the hacky way to get the correct list of upscalers + var empty_image = new Image(512, 512); + empty_image.src = + "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAFCAAAAABCAYAAAChpRsuAAAALklEQVR42u3BAQ0AAAgDoJvc6LeHAybtBgAAAAAAAAAAAAAAAAAAAAAAAAB47QD2wAJ/LnnqGgAAAABJRU5ErkJggg=="; //transparent pixel + var purposefully_incorrect_data = { + "resize-mode": 0, // 0 = just resize, 1 = crop and resize, 2 = resize and fill i assume based on theimg2img tabs options + upscaling_resize: 2, + upscaler_1: "fake_upscaler", + image: empty_image.src, + }; + + fetch(extras_url, { + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify(purposefully_incorrect_data), + }) + .then((response) => response.json()) + .then((data) => { + console.log("purposefully_incorrect_data response, ignore above error"); + // result = purposefully_incorrect_data response: Invalid upscaler, needs to be on of these: None , Lanczos , Nearest , LDSR , BSRGAN , R-ESRGAN General 4xV3 , R-ESRGAN 4x+ Anime6B , ScuNET , ScuNET PSNR , SwinIR_4x + let upscalers = data.detail.split(": ")[1].trim().split(" , "); // converting the result to a list of upscalers + for (var i = 0; i < upscalers.length; i++) { + // if(upscalers[i] == "LDSR") continue; // Skip LDSR, see reason in the first comment // readded because worksonmymachine.jpg but leaving it here in case of, uh, future disaster? + var option = document.createElement("option"); + option.text = upscalers[i]; + option.value = upscalers[i]; + upscalerSelect.add(option); + } + }); + + /* THE NON HACKY WAY THAT I SIMPLY COULD NOT GET TO PRODUCE A LIST WITHOUT NON WORKING UPSCALERS, FEEL FREE TO TRY AND FIGURE IT OUT + + var url = document.getElementById("host").value + "/sdapi/v1/upscalers"; + var realesrgan_url = document.getElementById("host").value + "/sdapi/v1/realesrgan-models"; + + // get upscalers + fetch(url) + .then((response) => response.json()) + .then((data) => { + console.log(data); + + for (var i = 0; i < data.length; i++) { + var option = document.createElement("option"); + + if (data[i].name.includes("ESRGAN") || data[i].name.includes("LDSR")) { + continue; + } + option.text = data[i].name; + upscalerSelect.add(option); + } + }) + .catch((error) => { + alert( + "Error getting upscalers, please check console for additional info\n" + + error + ); + }); + // fetch realesrgan models separately + fetch(realesrgan_url) + .then((response) => response.json()) + .then((data) => { + var model = data; + for(var i = 0; i < model.length; i++){ + let option = document.createElement("option"); + option.text = model[i].name; + option.value = model[i].name; + upscalerSelect.add(option); + + } + + }) + */ +} + +async function getModels() { + var modelSelect = document.getElementById("models"); + var url = document.getElementById("host").value + "/sdapi/v1/sd-models"; + await fetch(url) + .then((response) => response.json()) + .then((data) => { + //console.log(data); All models + for (var i = 0; i < data.length; i++) { + var option = document.createElement("option"); + option.text = data[i].model_name; + option.value = data[i].title; + modelSelect.add(option); + } + }); + + // get currently loaded model + + await fetch(document.getElementById("host").value + "/sdapi/v1/options") + .then((response) => response.json()) + .then((data) => { + var model = data.sd_model_checkpoint; + console.log("Current model: " + model); + modelSelect.value = model; + }); +} + +function changeModel() { + // change the model + console.log("changing model to " + document.getElementById("models").value); + var model_title = document.getElementById("models").value; + var payload = { + sd_model_checkpoint: model_title, + }; + var url = document.getElementById("host").value + "/sdapi/v1/options/"; + fetch(url, { + method: "POST", + mode: "cors", // no-cors, *cors, same-origin + cache: "default", // *default, no-cache, reload, force-cache, only-if-cached + credentials: "same-origin", // include, *same-origin, omit + redirect: "follow", // manual, *follow, error + referrerPolicy: "no-referrer", // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify(payload), + }) + .then((response) => response.json()) + .then(() => { + alert("Model changed to " + model_title); + }) + .catch((error) => { + alert( + "Error changing model, please check console for additional info\n" + + error + ); + }); +} + +function getSamplers() { + var samplerSelect = document.getElementById("samplerSelect"); + var url = document.getElementById("host").value + "/sdapi/v1/samplers"; + fetch(url) + .then((response) => response.json()) + .then((data) => { + //console.log(data); All samplers + for (var i = 0; i < data.length; i++) { + // PLMS SAMPLER DOES NOT WORK FOR ANY IMAGES BEYOND FOR THE INITIAL IMAGE (for me at least), GIVES ASGI Exception; AttributeError: 'PLMSSampler' object has no attribute 'stochastic_encode' + + var option = document.createElement("option"); + option.text = data[i].name; + option.value = data[i].name; + samplerSelect.add(option); + } + if (localStorage.getItem("sampler") != null) { + samplerSelect.value = localStorage.getItem("sampler"); + } else { + // needed now, as hardcoded sampler cant be guaranteed to be in the list + samplerSelect.value = data[0].name; + localStorage.setItem("sampler", samplerSelect.value); + } + }) + .catch((error) => { + alert( + "Error getting samplers, please check console for additional info\n" + + error + ); + }); +} +async function upscaleAndDownload() { + // Future improvements: some upscalers take a while to upscale, so we should show a loading bar or something, also a slider for the upscale amount + + // get cropped canvas, send it to upscaler, download result + var upscale_factor = 2; // TODO: make this a user input 1.x - 4.0 or something + var upscaler = document.getElementById("upscalers").value; + var croppedCanvas = cropCanvas(imgCanvas); + if (croppedCanvas != null) { + var upscaler = document.getElementById("upscalers").value; + var url = + document.getElementById("host").value + "/sdapi/v1/extra-single-image/"; + var imgdata = croppedCanvas.toDataURL("image/png"); + var data = { + "resize-mode": 0, // 0 = just resize, 1 = crop and resize, 2 = resize and fill i assume based on theimg2img tabs options + upscaling_resize: upscale_factor, + upscaler_1: upscaler, + image: imgdata, + }; + console.log(data); + await fetch(url, { + method: "POST", + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + body: JSON.stringify(data), + }) + .then((response) => response.json()) + .then((data) => { + console.log(data); + var link = document.createElement("a"); + link.download = + new Date() + .toISOString() + .slice(0, 19) + .replace("T", " ") + .replace(":", " ") + + " openOutpaint image upscaler_" + + upscaler + + ".png"; + link.href = "data:image/png;base64," + data["image"]; + link.click(); + }); + } +} + +function loadSettings() { + // set default values if not set + var _prompt = + localStorage.getItem("prompt") == null + ? "ocean floor scientific expedition, underwater wildlife" + : localStorage.getItem("prompt"); + var _negprompt = + localStorage.getItem("neg_prompt") == null + ? "people, person, humans, human, divers, diver, glitch, error, text, watermark, bad quality, blurry" + : localStorage.getItem("neg_prompt"); + var _sampler = + localStorage.getItem("sampler") == null + ? "DDIM" + : localStorage.getItem("sampler"); + var _mask_blur = + localStorage.getItem("mask_blur") == null + ? 0 + : localStorage.getItem("mask_blur"); + var _seed = + localStorage.getItem("seed") == null ? -1 : localStorage.getItem("seed"); + var _enable_hr = Boolean( + localStorage.getItem("enable_hr") == (null || "false") + ? false + : localStorage.getItem("enable_hr") + ); + var _enable_erase = Boolean( + localStorage.getItem("enable_erase") == (null || "false") + ? false + : localStorage.getItem("enable_erase") + ); + var _overmask_px = + localStorage.getItem("overmask_px") == null + ? 0 + : localStorage.getItem("overmask_px"); + + // set the values into the UI + document.getElementById("prompt").value = String(_prompt); + document.getElementById("prompt").title = String(_prompt); + document.getElementById("negPrompt").value = String(_negprompt); + document.getElementById("negPrompt").title = String(_negprompt); + document.getElementById("samplerSelect").value = String(_sampler); + document.getElementById("maskBlur").value = Number(_mask_blur); + document.getElementById("seed").value = Number(_seed); + document.getElementById("cbxHRFix").checked = Boolean(_enable_hr); + // document.getElementById("overMaskPx").value = Number(_overmask_px); +} + +document.getElementById("mainHSplit").addEventListener("wheel", (evn) => { + evn.preventDefault(); +});