diff --git a/css/ui/generic.css b/css/ui/generic.css
index eae9671..a7c2271 100644
--- a/css/ui/generic.css
+++ b/css/ui/generic.css
@@ -126,20 +126,33 @@ select > option:checked::after {
}
/* Icon button */
-.ui.squaer {
+.ui.square {
aspect-ratio: 1;
}
-.ui.button.icon {
- display: flex;
- align-items: stretch;
-
+.ui.button {
cursor: pointer;
padding: 0;
margin: 0;
border: 0;
- background-color: transparent;
+ color: var(--c-text);
+ background-color: var(--c-primary);
+ transition-duration: 50ms;
+}
+
+.ui.button:hover {
+ background-color: var(--c-hover);
+}
+
+.ui.button:active {
+ background-color: var(--c-hover);
+ filter: brightness(120%);
+}
+
+.ui.button.icon {
+ display: flex;
+ align-items: stretch;
}
.ui.button.icon > *:first-child {
@@ -155,7 +168,3 @@ select > option:checked::after {
mask-repeat: no-repeat;
background-color: var(--c-text);
}
-
-.ui.button.icon:hover {
- background-color: var(--c-hover);
-}
diff --git a/css/ui/tool/dream.css b/css/ui/tool/dream.css
new file mode 100644
index 0000000..784ed59
--- /dev/null
+++ b/css/ui/tool/dream.css
@@ -0,0 +1,3 @@
+.dream-interrupt-btn {
+ width: 100px;
+}
diff --git a/index.html b/index.html
index f827cfd..14ac634 100644
--- a/index.html
+++ b/index.html
@@ -17,6 +17,7 @@
+
diff --git a/js/ui/tool/dream.js b/js/ui/tool/dream.js
index 849889c..1e6433e 100644
--- a/js/ui/tool/dream.js
+++ b/js/ui/tool/dream.js
@@ -1,12 +1,14 @@
let blockNewImages = false;
+let generating = false;
/**
* Starts progress monitoring bar
*
* @param {BoundingBox} bb Bouding Box to draw progress to
+ * @param {(data: object) => void} [oncheck] Callback function for when a progress check returns
* @returns {() => void}
*/
-const _monitorProgress = (bb) => {
+const _monitorProgress = (bb, oncheck = null) => {
const minDelay = 1000;
const apiURL = `${host}${url}progress?skip_current_image=true`;
@@ -33,6 +35,8 @@ const _monitorProgress = (bb) => {
/** @type {StableDiffusionProgressResponse} */
const data = await response.json();
+ oncheck && oncheck(data);
+
// Draw Progress Bar
layer.ctx.fillStyle = "#5F5";
layer.ctx.fillRect(1, 1, bb.w * data.progress, 10);
@@ -81,6 +85,7 @@ const _dream = async (endpoint, request) => {
/** @type {StableDiffusionResponse} */
let data = null;
try {
+ generating = true;
const response = await fetch(apiURL, {
method: "POST",
headers: {
@@ -92,6 +97,7 @@ const _dream = async (endpoint, request) => {
data = await response.json();
} finally {
+ generating = false;
}
return data.images;
@@ -103,9 +109,15 @@ const _dream = async (endpoint, request) => {
* @param {"txt2img" | "img2img"} endpoint Endpoint to send the request to
* @param {StableDiffusionRequest} request Stable diffusion request
* @param {BoundingBox} bb Generated image placement location
+ * @param {number} [drawEvery=0.2 / request.n_iter] Percentage delta to draw progress at (by default 20% of each iteration)
* @returns {Promise}
*/
-const _generate = async (endpoint, request, bb) => {
+const _generate = async (
+ endpoint,
+ request,
+ bb,
+ drawEvery = 0.2 / request.n_iter
+) => {
const requestCopy = {...request};
// Images to select through
@@ -120,26 +132,47 @@ const _generate = async (endpoint, request, bb) => {
after: maskPaintLayer,
});
- const redraw = () => {
+ const makeElement = (type, x, y) => {
+ const el = document.createElement(type);
+ el.style.position = "absolute";
+ el.style.left = `${x}px`;
+ el.style.top = `${y}px`;
+
+ // We can use the input element to add interactible html elements in the world
+ imageCollection.inputElement.appendChild(el);
+
+ return el;
+ };
+
+ const redraw = (url = images[at]) => {
+ if (!url) return;
+
const image = new Image();
- image.src = "data:image/png;base64," + images[at];
+ image.src = "data:image/png;base64," + url;
image.addEventListener("load", () => {
layer.ctx.clearRect(0, 0, layer.canvas.width, layer.canvas.height);
- if (images[at])
- layer.ctx.drawImage(
- image,
- 0,
- 0,
- image.width,
- image.height,
- bb.x,
- bb.y,
- bb.w,
- bb.h
- );
+ layer.ctx.drawImage(
+ image,
+ 0,
+ 0,
+ image.width,
+ image.height,
+ bb.x,
+ bb.y,
+ bb.w,
+ bb.h
+ );
});
};
+ // Add Interrupt Button
+ const interruptButton = makeElement("button", bb.x + bb.w - 100, bb.y + bb.h);
+ interruptButton.classList.add("dream-interrupt-btn");
+ interruptButton.textContent = "Interrupt";
+ interruptButton.addEventListener("click", () => {
+ fetch(`${host}${url}interrupt`, {method: "POST"});
+ interruptButton.disabled = true;
+ });
const stopMarchingAnts = march(bb);
// First Dream Run
@@ -148,8 +181,28 @@ const _generate = async (endpoint, request, bb) => {
let stopProgress = null;
try {
- stopProgress = _monitorProgress(bb);
+ let stopDrawingStatus = false;
+ let lastProgress = 0;
+ let nextCP = drawEvery;
+ stopProgress = _monitorProgress(bb, (data) => {
+ if (stopDrawingStatus) return;
+
+ if (lastProgress < nextCP && data.progress >= nextCP) {
+ nextCP += drawEvery;
+ fetch(`${host}${url}progress?skip_current_image=false`).then(
+ async (response) => {
+ if (stopDrawingStatus) return;
+ const imagedata = await response.json();
+ redraw(imagedata.current_image);
+ }
+ );
+ }
+ lastProgress = data.progress;
+ });
+
+ imageCollection.inputElement.appendChild(interruptButton);
images.push(...(await _dream(endpoint, requestCopy)));
+ stopDrawingStatus = true;
} catch (e) {
alert(
`Error generating images. Please try again or see consolde for more details`
@@ -158,6 +211,7 @@ const _generate = async (endpoint, request, bb) => {
console.warn(e);
} finally {
stopProgress();
+ imageCollection.inputElement.removeChild(interruptButton);
}
// Image navigation
@@ -196,6 +250,8 @@ const _generate = async (endpoint, request, bb) => {
const makeMore = async () => {
try {
stopProgress = _monitorProgress(bb);
+ interruptButton.disabled = false;
+ imageCollection.inputElement.appendChild(interruptButton);
images.push(...(await _dream(endpoint, requestCopy)));
imageindextxt.textContent = `${at + 1}/${images.length}`;
} catch (e) {
@@ -206,6 +262,7 @@ const _generate = async (endpoint, request, bb) => {
console.warn(e);
} finally {
stopProgress();
+ imageCollection.inputElement.removeChild(interruptButton);
}
};
@@ -265,18 +322,6 @@ const _generate = async (endpoint, request, bb) => {
keyboard.listen.onkeyclick.clear(onarrow);
};
- const makeElement = (type, x, y) => {
- const el = document.createElement(type);
- el.style.position = "absolute";
- el.style.left = `${x}px`;
- el.style.top = `${y}px`;
-
- // We can use the input element to add interactible html elements in the world
- imageCollection.inputElement.appendChild(el);
-
- return el;
- };
-
redraw();
imageSelectMenu = makeElement("div", bb.x, bb.y + bb.h);
@@ -541,8 +586,6 @@ const dream_img2img_callback = (evn, state) => {
// Get visible pixels
const visibleCanvas = uil.getVisible(bb);
- console.debug(visibleCanvas);
-
// Do nothing if no image exists
if (isCanvasBlank(0, 0, bb.w, bb.h, visibleCanvas)) return;
@@ -670,7 +713,7 @@ const dream_img2img_callback = (evn, state) => {
/**
* Dream and img2img tools
*/
-const _reticle_draw = (evn, state) => {
+const _reticle_draw = (evn, state, textStyle = "#FFF5") => {
const bb = getBoundingBox(
evn.x,
evn.y,
@@ -678,21 +721,56 @@ const _reticle_draw = (evn, state) => {
state.cursorSize,
state.snapToGrid && basePixelCount
);
-
- const cvp = viewport.canvasToView(evn.x, evn.y);
const bbvp = {
...viewport.canvasToView(bb.x, bb.y),
w: viewport.zoom * bb.w,
h: viewport.zoom * bb.h,
};
+ uiCtx.save();
+
// draw targeting square reticle thingy cursor
uiCtx.lineWidth = 1;
uiCtx.strokeStyle = "#FFF";
uiCtx.strokeRect(bbvp.x, bbvp.y, bbvp.w, bbvp.h); //origin is middle of the frame
+ // Draw width and height
+ uiCtx.textAlign = "center";
+ uiCtx.fillStyle = textStyle;
+ uiCtx.font = `bold 20px Open Sans`;
+ uiCtx.translate(bbvp.x + bbvp.w / 2, bbvp.y + bbvp.h / 2);
+ const xshrink = Math.min(
+ 1,
+ (bbvp.w - 30) / uiCtx.measureText(`${state.cursorSize}px`).width
+ );
+ const yshrink = Math.min(
+ 1,
+ (bbvp.h - 30) / uiCtx.measureText(`${state.cursorSize}px`).width
+ );
+ uiCtx.font = `bold ${20 * xshrink}px Open Sans`;
+ uiCtx.fillText(
+ `${state.cursorSize}px`,
+ 0,
+ bbvp.h / 2 - 10 * xshrink,
+ state.cursorSize
+ );
+ uiCtx.rotate(-Math.PI / 2);
+ uiCtx.font = `bold ${20 * yshrink}px Open Sans`;
+ uiCtx.fillText(
+ `${state.cursorSize}px`,
+ 0,
+ bbvp.h / 2 - 10 * yshrink,
+ state.cursorSize
+ );
+
+ uiCtx.restore();
+
return () => {
+ uiCtx.save();
+
uiCtx.clearRect(bbvp.x - 10, bbvp.y - 10, bbvp.w + 20, bbvp.h + 20);
+
+ uiCtx.restore();
};
};
@@ -701,7 +779,6 @@ const _reticle_draw = (evn, state) => {
*/
const _dream_onwheel = (evn, state) => {
- state.mousemovecb(evn);
if (!evn.evn.ctrlKey) {
const v =
state.cursorSize -
@@ -761,10 +838,26 @@ const dreamTool = () =>
state.erasePrevReticle = () =>
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
- state.mousemovecb = (evn) => {
- state.erasePrevReticle();
- state.erasePrevReticle = _reticle_draw(evn, state);
+ let lastMouseMove = {
+ ...mouse.coords.world.pos,
};
+
+ state.mousemovecb = (evn) => {
+ lastMouseMove = evn;
+ state.erasePrevReticle();
+ const style =
+ state.cursorSize > stableDiffusionData.width
+ ? "#FBB5"
+ : state.cursorSize < stableDiffusionData.width
+ ? "#BFB5"
+ : "#FFF5";
+ state.erasePrevReticle = _reticle_draw(evn, state, style);
+ };
+
+ state.redraw = () => {
+ state.mousemovecb(lastMouseMove);
+ };
+
state.wheelcb = (evn) => {
_dream_onwheel(evn, state);
};
@@ -887,7 +980,11 @@ const img2imgTool = () =>
state.erasePrevReticle = () =>
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
+ let lastMouseMove = {
+ ...mouse.coords.world.pos,
+ };
state.mousemovecb = (evn) => {
+ lastMouseMove = evn;
state.erasePrevReticle();
state.erasePrevReticle = _reticle_draw(evn, state);
const bb = getBoundingBox(
@@ -989,6 +1086,11 @@ const img2imgTool = () =>
);
}
};
+
+ state.redraw = () => {
+ state.mousemovecb(lastMouseMove);
+ };
+
state.wheelcb = (evn) => {
_dream_onwheel(evn, state);
};
@@ -1089,3 +1191,8 @@ const img2imgTool = () =>
shortcut: "I",
}
);
+
+window.onbeforeunload = async () => {
+ // Stop current generation on page close
+ if (generating) await fetch(`${host}${url}interrupt`, {method: "POST"});
+};