From 6e52680fa8ff8f246a8578c25a151111a77f24c9 Mon Sep 17 00:00:00 2001 From: cmdr2 Date: Fri, 28 Jul 2023 22:48:41 +0530 Subject: [PATCH] Fast in-place upscale and face fix buttons, with an option to undo the operations --- ui/index.html | 1 + ui/media/css/animations.css | 68 ++++++++++++++++++++++++++ ui/media/css/main.css | 17 +++++++ ui/media/js/engine.js | 68 ++++++++++++++++++++++++-- ui/media/js/main.js | 95 ++++++++++++++++++++++++++++++++----- 5 files changed, 232 insertions(+), 17 deletions(-) create mode 100644 ui/media/css/animations.css diff --git a/ui/index.html b/ui/index.html index 14cf25ec..776eb627 100644 --- a/ui/index.html +++ b/ui/index.html @@ -17,6 +17,7 @@ + diff --git a/ui/media/css/animations.css b/ui/media/css/animations.css new file mode 100644 index 00000000..71ee642f --- /dev/null +++ b/ui/media/css/animations.css @@ -0,0 +1,68 @@ +@keyframes ldio-8f673ktaleu-1 { + 0% { transform: rotate(0deg) } + 50% { transform: rotate(-45deg) } + 100% { transform: rotate(0deg) } +} +@keyframes ldio-8f673ktaleu-2 { + 0% { transform: rotate(180deg) } + 50% { transform: rotate(225deg) } + 100% { transform: rotate(180deg) } +} +.ldio-8f673ktaleu > div:nth-child(2) { + transform: translate(-15px,0); +} +.ldio-8f673ktaleu > div:nth-child(2) div { + position: absolute; + top: 20px; + left: 20px; + width: 60px; + height: 30px; + border-radius: 60px 60px 0 0; + background: #f3b72e; + animation: ldio-8f673ktaleu-1 1s linear infinite; + transform-origin: 30px 30px +} +.ldio-8f673ktaleu > div:nth-child(2) div:nth-child(2) { + animation: ldio-8f673ktaleu-2 1s linear infinite +} +.ldio-8f673ktaleu > div:nth-child(2) div:nth-child(3) { + transform: rotate(-90deg); + animation: none; +}@keyframes ldio-8f673ktaleu-3 { + 0% { transform: translate(95px,0); opacity: 0 } + 20% { opacity: 1 } + 100% { transform: translate(35px,0); opacity: 1 } +} +.ldio-8f673ktaleu > div:nth-child(1) { + display: block; +} +.ldio-8f673ktaleu > div:nth-child(1) div { + position: absolute; + top: 46px; + left: -4px; + width: 8px; + height: 8px; + border-radius: 50%; + background: #3869c5; + animation: ldio-8f673ktaleu-3 1s linear infinite +} +.ldio-8f673ktaleu > div:nth-child(1) div:nth-child(1) { animation-delay: -0.67s } +.ldio-8f673ktaleu > div:nth-child(1) div:nth-child(2) { animation-delay: -0.33s } +.ldio-8f673ktaleu > div:nth-child(1) div:nth-child(3) { animation-delay: 0s } +.loadingio-spinner-bean-eater-x0y3u8qky4n { + width: 58px; + height: 58px; + display: inline-block; + overflow: hidden; + background: none; +} +.ldio-8f673ktaleu { + width: 100%; + height: 100%; + position: relative; + transform: translateZ(0) scale(0.58); + backface-visibility: hidden; + transform-origin: 0 0; /* see note above */ +} +.ldio-8f673ktaleu div { box-sizing: content-box; } +/* generated by https://loading.io/ */ diff --git a/ui/media/css/main.css b/ui/media/css/main.css index f6be52c5..4d7a0f62 100644 --- a/ui/media/css/main.css +++ b/ui/media/css/main.css @@ -1753,3 +1753,20 @@ body.wait-pause { content: "Please restart Easy Diffusion!"; font-size: 10pt; } + +.imgContainer .spinner { + position: absolute; + left: 50%; + top: 50%; + transform: translate(-50%, -50%); + margin: 0; + padding: 0; + + background: var(--background-color3); + opacity: 0.8; + border-radius: 5px; + padding: 4pt; +} +.imgContainer .spinnerStatus { + font-size: 10pt; +} diff --git a/ui/media/js/engine.js b/ui/media/js/engine.js index c584a609..a11dd8b3 100644 --- a/ui/media/js/engine.js +++ b/ui/media/js/engine.js @@ -1056,11 +1056,26 @@ * @memberof Task */ async post(timeout = -1) { - let res = await super.post("/filter", timeout) - //this._setId(jsonResponse.task) + let jsonResponse = await super.post("/filter", timeout) + if (typeof jsonResponse?.task !== "number") { + console.warn("Endpoint error response: ", jsonResponse) + const event = Object.assign({ task: this }, jsonResponse) + await eventSource.fireEvent(EVENT_UNEXPECTED_RESPONSE, event) + if ("continueWith" in event) { + jsonResponse = await Promise.resolve(event.continueWith) + } + if (typeof jsonResponse?.task !== "number") { + const err = new Error(jsonResponse?.detail || "Endpoint response does not contains a task ID.") + this.abort(err) + throw err + } + } + this._setId(jsonResponse.task) + if (jsonResponse.stream) { + this.streamUrl = jsonResponse.stream + } this._setStatus(TaskStatus.waiting) - - return res + return jsonResponse } checkReqBody() {} enqueue(progressCallback) { @@ -1087,6 +1102,51 @@ yield progressCallback?.call(this, { detail: e.message }) throw e } + + try { + // Wait for task to start on server. + yield this.waitUntil({ + callback: function() { + return progressCallback?.call(this, {}) + }, + status: TaskStatus.processing, + }) + } catch (e) { + this.abort(err) + throw e + } + + // Task started! + // Open the reader. + const reader = this.reader + const task = this + reader.onError = function(response) { + if (progressCallback) { + task.abort(new Error(response.statusText)) + return progressCallback.call(task, { response, reader }) + } + return Task.prototype.onError.call(task, response) + } + yield progressCallback?.call(this, { reader }) + + //Start streaming the results. + const streamGenerator = reader.open() + let value = undefined + let done = undefined + yield progressCallback?.call(this, { stream: streamGenerator }) + do { + ;({ value, done } = yield streamGenerator.next()) + if (typeof value !== "object") { + continue + } + if (value.status !== undefined) { + yield progressCallback?.call(this, value) + if (value.status === "succeeded" || value.status === "failed") { + done = true + } + } + } while (!done) + return value } static start(task, progressCallback) { if (typeof task !== "object") { diff --git a/ui/media/js/main.js b/ui/media/js/main.js index 0b936066..3bfc3233 100644 --- a/ui/media/js/main.js +++ b/ui/media/js/main.js @@ -5,6 +5,9 @@ const MIN_GPUS_TO_SHOW_SELECTION = 2 const IMAGE_REGEX = new RegExp("data:image/[A-Za-z]+;base64") const htmlTaskMap = new WeakMap() +const spinnerPacmanHtml = + '
' + const taskConfigSetup = { taskConfig: { seed: { value: ({ seed }) => seed, label: "Seed" }, @@ -412,6 +415,7 @@ function showImages(reqBody, res, outputContainer, livePreview) { +
${spinnerPacmanHtml}
` outputContainer.appendChild(imageItemElem) @@ -488,6 +492,7 @@ function showImages(reqBody, res, outputContainer, livePreview) { const imageSeedLabel = imageItemElem.querySelector(".imgSeedLabel") imageSeedLabel.innerText = "Seed: " + req.seed + const imageUndoBuffer = [] let buttons = [ { text: "Use as Input", on_click: onUseAsInputClick }, [ @@ -505,8 +510,9 @@ function showImages(reqBody, res, outputContainer, livePreview) { { text: "Make Similar Images", on_click: onMakeSimilarClick }, { text: "Draw another 25 steps", on_click: onContinueDrawingClick }, [ - { text: "Upscale", on_click: onUpscaleClick, filter: (req, img) => !req.use_upscale }, - { text: "Fix Faces", on_click: onFixFacesClick, filter: (req, img) => !req.use_face_correction }, + { text: "Undo", on_click: onUndoFilter }, + { text: "Upscale", on_click: onUpscaleClick }, + { text: "Fix Faces", on_click: onFixFacesClick }, ], ] @@ -515,6 +521,13 @@ function showImages(reqBody, res, outputContainer, livePreview) { const imgItemInfo = imageItemElem.querySelector(".imgItemInfo") const img = imageItemElem.querySelector("img") + const spinner = imageItemElem.querySelector(".spinner") + const spinnerStatus = imageItemElem.querySelector(".spinnerStatus") + const tools = { + spinner: spinner, + spinnerStatus: spinnerStatus, + undoBuffer: imageUndoBuffer, + } const createButton = function(btnInfo) { if (Array.isArray(btnInfo)) { const wrapper = document.createElement("div") @@ -540,8 +553,12 @@ function showImages(reqBody, res, outputContainer, livePreview) { if (btnInfo.on_click || !isLabel) { newButton.addEventListener("click", function(event) { - btnInfo.on_click(req, img, event) + btnInfo.on_click.bind(newButton)(req, img, event, tools) }) + if (btnInfo.on_click === onUndoFilter) { + tools["undoButton"] = newButton + newButton.classList.add("displayNone") + } } if (btnInfo.class !== undefined) { @@ -656,16 +673,64 @@ function enqueueImageVariationTask(req, img, reqDiff) { createTask(newTaskRequest) } -function onUpscaleClick(req, img) { - enqueueImageVariationTask(req, img, { - use_upscale: upscaleModelField.value, +function applyInlineFilter(filterName, path, filterParams, img, statusText, tools) { + const filterReq = { + image: img.src, + filter: filterName, + model_paths: {}, + filter_params: filterParams, + } + filterReq.model_paths[filterName] = path + + tools.spinnerStatus.innerText = statusText + tools.spinner.classList.remove("displayNone") + + SD.filter(filterReq, (e) => { + if (e.status === "succeeded") { + let prevImg = img.src + img.src = e.output[0] + tools.spinner.classList.add("displayNone") + tools.undoButton.classList.remove("displayNone") + + if (prevImg.length > 0) { + tools.undoBuffer.push(prevImg) + } + } else if (e.status == "failed") { + alert("Error running upscale: " + e.detail) + tools.spinner.classList.add("displayNone") + } }) } -function onFixFacesClick(req, img) { - enqueueImageVariationTask(req, img, { - use_face_correction: gfpganModelField.value, - }) +function onUndoFilter(req, img, e, tools) { + if (tools.undoBuffer.length === 0) { + this.classList.add("displayNone") + return + } + + let src = tools.undoBuffer.pop() + if (src.length > 0) { + img.src = src + } + + if (tools.undoBuffer.length === 0) { + this.classList.add("displayNone") + } +} + +function onUpscaleClick(req, img, e, tools) { + let path = upscaleModelField.value + let scale = parseInt(upscaleAmountField.value) + let filterName = path.toLowerCase().includes("realesrgan") ? "realesrgan" : "latent_upscaler" + let statusText = "Upscaling by " + scale + "x using " + filterName + applyInlineFilter(filterName, path, { scale: scale }, img, statusText, tools) +} + +function onFixFacesClick(req, img, e, tools) { + let path = gfpganModelField.value + let filterName = path.toLowerCase().includes("gfpgan") ? "gfpgan" : "codeformer" + let statusText = "Fixing faces with " + filterName + applyInlineFilter(filterName, path, {}, img, statusText, tools) } function onContinueDrawingClick(req, img) { @@ -909,7 +974,9 @@ function onTaskCompleted(task, reqBody, instance, outputContainer, stepUpdate) { Windows or Linux.
3. Try restarting your computer.
` - } else if (msg.includes("RuntimeError: output with shape [320, 320] doesn't match the broadcast shape")) { + } else if ( + msg.includes("RuntimeError: output with shape [320, 320] doesn't match the broadcast shape") + ) { msg += `

Reason: You tried to use a LORA that was trained for a different Stable Diffusion model version!

@@ -2171,7 +2238,10 @@ function updateEmbeddingsList(filter = "") { } else { let subdir = html(m[1], prefix + m[0] + "/", filter) if (subdir != "") { - folders += `

${prefix}${m[0]}

` + subdir + '
' + folders += + `

${prefix}${m[0]}

` + + subdir + + "
" } } }) @@ -2293,7 +2363,6 @@ embeddingsCollapsiblesBtn.addEventListener("click", (e) => { } }) - if (testDiffusers.checked) { document.getElementById("embeddings-container").classList.remove("displayNone") }