Fast in-place upscale and face fix buttons, with an option to undo the operations

This commit is contained in:
cmdr2 2023-07-28 22:48:41 +05:30
parent 7f32c531d7
commit 6e52680fa8
5 changed files with 232 additions and 17 deletions

View File

@ -17,6 +17,7 @@
<link rel="stylesheet" href="/media/css/searchable-models.css">
<link rel="stylesheet" href="/media/css/image-modal.css">
<link rel="stylesheet" href="/media/css/plugins.css">
<link rel="stylesheet" href="/media/css/animations.css">
<link rel="manifest" href="/media/manifest.webmanifest">
<script src="/media/js/jquery-3.6.1.min.js"></script>
<script src="/media/js/jquery-confirm.min.js"></script>

View File

@ -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/ */

View File

@ -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;
}

View File

@ -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") {

View File

@ -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 =
'<div class="loadingio-spinner-bean-eater-x0y3u8qky4n"><div class="ldio-8f673ktaleu"><div><div></div><div></div><div></div></div><div><div></div><div></div><div></div></div></div></div>'
const taskConfigSetup = {
taskConfig: {
seed: { value: ({ seed }) => seed, label: "Seed" },
@ -412,6 +415,7 @@ function showImages(reqBody, res, outputContainer, livePreview) {
</div>
<button class="imgPreviewItemClearBtn image_clear_btn"><i class="fa-solid fa-xmark"></i></button>
<span class="img_bottom_label"></span>
<div class="spinner displayNone"><center>${spinnerPacmanHtml}</center><div class="spinnerStatus"></div></div>
</div>
`
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) {
<a href="https://www.ibm.com/docs/en/opw/8.2.0?topic=tuning-optional-increasing-paging-file-size-windows-computers" target="_blank">Windows</a> or
<a href="https://linuxhint.com/increase-swap-space-linux/" target="_blank">Linux</a>.<br/>
3. Try restarting your computer.<br/>`
} 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 += `<br/><br/>
<b>Reason</b>: You tried to use a LORA that was trained for a different Stable Diffusion model version!
<br/><br/>
@ -2171,7 +2238,10 @@ function updateEmbeddingsList(filter = "") {
} else {
let subdir = html(m[1], prefix + m[0] + "/", filter)
if (subdir != "") {
folders += `<div class="embedding-category"><h4 class="collapsible">${prefix}${m[0]}</h4><div class="collapsible-content">` + subdir + '</div></div>'
folders +=
`<div class="embedding-category"><h4 class="collapsible">${prefix}${m[0]}</h4><div class="collapsible-content">` +
subdir +
"</div></div>"
}
}
})
@ -2293,7 +2363,6 @@ embeddingsCollapsiblesBtn.addEventListener("click", (e) => {
}
})
if (testDiffusers.checked) {
document.getElementById("embeddings-container").classList.remove("displayNone")
}