diff --git a/.github.bkp/workflows/autoformat.yml b/.github/workflows/autoformat.yml similarity index 82% rename from .github.bkp/workflows/autoformat.yml rename to .github/workflows/autoformat.yml index ea58136..bad7bd1 100644 --- a/.github.bkp/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] @@ -13,4 +16,5 @@ jobs: - name: Prettify uses: creyD/prettier_action@v4.2 with: + ref: ${{ github.head_ref }} prettier_options: --write **/*.{js,html,css,md} diff --git a/index.html b/index.html index 2bc6877..3e07789 100644 --- a/index.html +++ b/index.html @@ -1,230 +1,230 @@ - - - - - openOutpaint 🐠 - - - - - - - - - - - - - - - - - - - -
-
- openOutpaint 🐠 -
- -
- - -
-
History
- -
- - -
-
- -
-
-
-
- - -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + openOutpaint 🐠 + + + + + + + + + + + + + + + + + + + +
+
+ openOutpaint 🐠 +
+ +
+ + +
+
History
+ +
+ + +
+
+ +
+
+
+
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/js/index.js b/js/index.js index 4727faf..c51c209 100644 --- a/js/index.js +++ b/js/index.js @@ -1,853 +1,853 @@ -//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 returnedImages; -var imageIndex = 0; -var tmpImgXYWH = {}; -var host = ""; -var url = "/sdapi/v1/"; -var endpoint = "txt2img"; -var frameX = 512; -var frameY = 512; -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 = {}; - -// -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("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 - host = current.endsWith("/") - ? current.substring(0, current.length - 1) - : current; - } else { - requestHost( - "This seems to be the first time you are using openOutpaint! Please set your host here:" - ); - } -} - -function testHostConnection() { - class CheckInProgressError extends Error {} - - 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 newImage(evt) { - clearPaintedMask(); - clearBackupMask(); - 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 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) { - const expanded = {...bb}; - expanded.x--; - expanded.y--; - expanded.w += 2; - expanded.h += 2; - - // Get temporary layer to draw marching ants - const layer = imageCollection.registerLayer(null, { - bb: expanded, - }); - layer.canvas.style.imageRendering = "pixelated"; - let offset = 0; - - const interval = setInterval(() => { - drawMarchingAnts(layer.ctx, bb, offset++); - offset %= 12; - }, 20); - - return () => { - clearInterval(interval); - imageCollection.deleteLayer(layer); - }; -} - -function drawMarchingAnts(ctx, bb, offset) { - ctx.clearRect(0, 0, bb.w + 2, bb.h + 2); - ctx.strokeStyle = "#FFFFFFFF"; //"#55000077"; - ctx.strokeWidth = "2px"; - ctx.setLineDash([4, 2]); - ctx.lineDashOffset = -offset; - ctx.strokeRect(1, 1, bb.w, bb.h); -} - -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 < bgLayer.canvas.width; x += 64) { - for (var y = 0; y < bgLayer.canvas.height; y += 64) { - bgLayer.ctx.fillStyle = - (x + y) % 128 === 0 ? lightTileColor : darkTileColor; - bgLayer.ctx.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); -} - -imageCollection.element.addEventListener( - "wheel", - (evn) => { - evn.preventDefault(); - }, - {passive: false} -); - -imageCollection.element.addEventListener( - "contextmenu", - (evn) => { - evn.preventDefault(); - }, - {passive: false} -); +//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 returnedImages; +var imageIndex = 0; +var tmpImgXYWH = {}; +var host = ""; +var url = "/sdapi/v1/"; +var endpoint = "txt2img"; +var frameX = 512; +var frameY = 512; +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 = {}; + +// +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("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 + host = current.endsWith("/") + ? current.substring(0, current.length - 1) + : current; + } else { + requestHost( + "This seems to be the first time you are using openOutpaint! Please set your host here:" + ); + } +} + +function testHostConnection() { + class CheckInProgressError extends Error {} + + 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 newImage(evt) { + clearPaintedMask(); + clearBackupMask(); + 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 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) { + const expanded = {...bb}; + expanded.x--; + expanded.y--; + expanded.w += 2; + expanded.h += 2; + + // Get temporary layer to draw marching ants + const layer = imageCollection.registerLayer(null, { + bb: expanded, + }); + layer.canvas.style.imageRendering = "pixelated"; + let offset = 0; + + const interval = setInterval(() => { + drawMarchingAnts(layer.ctx, bb, offset++); + offset %= 12; + }, 20); + + return () => { + clearInterval(interval); + imageCollection.deleteLayer(layer); + }; +} + +function drawMarchingAnts(ctx, bb, offset) { + ctx.clearRect(0, 0, bb.w + 2, bb.h + 2); + ctx.strokeStyle = "#FFFFFFFF"; //"#55000077"; + ctx.strokeWidth = "2px"; + ctx.setLineDash([4, 2]); + ctx.lineDashOffset = -offset; + ctx.strokeRect(1, 1, bb.w, bb.h); +} + +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 < bgLayer.canvas.width; x += 64) { + for (var y = 0; y < bgLayer.canvas.height; y += 64) { + bgLayer.ctx.fillStyle = + (x + y) % 128 === 0 ? lightTileColor : darkTileColor; + bgLayer.ctx.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); +} + +imageCollection.element.addEventListener( + "wheel", + (evn) => { + evn.preventDefault(); + }, + {passive: false} +); + +imageCollection.element.addEventListener( + "contextmenu", + (evn) => { + evn.preventDefault(); + }, + {passive: false} +);