dream queueing now supported

- Image navigation shortcuts work for all simultaneously (probably not
   optimal)

This is for #89. Some things should be ironed out, such as adding a
cancel button for future jobs and an order indicator maybe?

Signed-off-by: Victor Seiji Hariki <victorseijih@gmail.com>
This commit is contained in:
Victor Seiji Hariki 2022-12-12 00:50:25 -03:00
parent e3be9010f2
commit f279c8457b
2 changed files with 277 additions and 235 deletions

View file

@ -1,4 +1,6 @@
let blockNewImages = false; let blockNewImages = false;
let generationQueue = [];
let generationAreas = new Set();
let generating = false; let generating = false;
/** /**
@ -118,7 +120,50 @@ const _generate = async (
bb, bb,
drawEvery = 0.2 / request.n_iter drawEvery = 0.2 / request.n_iter
) => { ) => {
const requestCopy = {...request}; const requestCopy = JSON.parse(JSON.stringify(request));
// Block requests to identical areas
const areaid = `${bb.x}-${bb.y}-${bb.w}-${bb.h}`;
if (generationAreas.has(areaid)) return;
generationAreas.add(areaid);
// Await for queue
const waitQueue = async () => {
const stopQueueMarchingAnts = march(bb, {style: "#AAF"});
let qPromise = null;
let qResolve = null;
await new Promise((finish) => {
// Will be this request's (kind of) semaphore
qPromise = new Promise((r) => (qResolve = r));
generationQueue.push(qPromise);
// Wait for last generation to end
if (generationQueue.length > 1) {
(async () => {
await generationQueue[generationQueue.length - 2];
finish();
})();
} else {
// If this is the first, just continue
finish();
}
});
stopQueueMarchingAnts();
return {promise: qPromise, resolve: qResolve};
};
const nextQueue = (queueEntry) => {
const generationIndex = generationQueue.findIndex(
(v) => v === queueEntry.promise
);
generationQueue.splice(generationIndex, 1);
queueEntry.resolve();
};
const initialQ = await waitQueue();
// Images to select through // Images to select through
let at = 0; let at = 0;
@ -253,6 +298,7 @@ const _generate = async (
}; };
const makeMore = async () => { const makeMore = async () => {
const moreQ = await waitQueue();
try { try {
stopProgress = _monitorProgress(bb); stopProgress = _monitorProgress(bb);
interruptButton.disabled = false; interruptButton.disabled = false;
@ -269,6 +315,8 @@ const _generate = async (
stopProgress(); stopProgress();
imageCollection.inputElement.removeChild(interruptButton); imageCollection.inputElement.removeChild(interruptButton);
} }
nextQueue(moreQ);
}; };
const discardImg = async () => { const discardImg = async () => {
@ -342,8 +390,9 @@ const _generate = async (
stopMarchingAnts(); stopMarchingAnts();
imageCollection.inputElement.removeChild(imageSelectMenu); imageCollection.inputElement.removeChild(imageSelectMenu);
imageCollection.deleteLayer(layer); imageCollection.deleteLayer(layer);
blockNewImages = false;
keyboard.listen.onkeyclick.clear(onarrow); keyboard.listen.onkeyclick.clear(onarrow);
// Remove area from no-generate list
generationAreas.delete(areaid);
}; };
redraw(); redraw();
@ -414,6 +463,8 @@ const _generate = async (
saveImg(); saveImg();
}); });
imageSelectMenu.appendChild(savebtn); imageSelectMenu.appendChild(savebtn);
nextQueue(initialQ);
}; };
/** /**
@ -423,46 +474,75 @@ const _generate = async (
* @param {*} state * @param {*} state
*/ */
const dream_generate_callback = async (evn, state) => { const dream_generate_callback = async (evn, state) => {
if (!blockNewImages) { const bb = getBoundingBox(
const bb = getBoundingBox( evn.x,
evn.x, evn.y,
evn.y, state.cursorSize,
state.cursorSize, state.cursorSize,
state.cursorSize, state.snapToGrid && basePixelCount
state.snapToGrid && basePixelCount );
// Build request to the API
const request = {};
Object.assign(request, stableDiffusionData);
// Load prompt (maybe we should add some events so we don't have to do this)
request.prompt = document.getElementById("prompt").value;
request.negative_prompt = document.getElementById("negPrompt").value;
// Get visible pixels
const visibleCanvas = uil.getVisible(bb);
// Use txt2img if canvas is blank
if (isCanvasBlank(0, 0, bb.w, bb.h, visibleCanvas)) {
// Dream
_generate("txt2img", request, bb);
} else {
// Use img2img if not
// Temporary canvas for init image and mask generation
const auxCanvas = document.createElement("canvas");
auxCanvas.width = request.width;
auxCanvas.height = request.height;
const auxCtx = auxCanvas.getContext("2d");
auxCtx.fillStyle = "#000F";
// Get init image
auxCtx.fillRect(0, 0, request.width, request.height);
auxCtx.drawImage(
visibleCanvas,
0,
0,
bb.w,
bb.h,
0,
0,
request.width,
request.height
); );
request.init_images = [auxCanvas.toDataURL()];
// Build request to the API // Get mask image
const request = {}; auxCtx.fillStyle = "#000F";
Object.assign(request, stableDiffusionData); auxCtx.fillRect(0, 0, request.width, request.height);
if (state.invertMask) {
// overmasking by definition is entirely pointless with an inverted mask outpaint
// since it should explicitly avoid brushed masks too, we just won't even bother
auxCtx.globalCompositeOperation = "destination-in";
auxCtx.drawImage(
maskPaintCanvas,
bb.x,
bb.y,
bb.w,
bb.h,
0,
0,
request.width,
request.height
);
// Load prompt (maybe we should add some events so we don't have to do this) auxCtx.globalCompositeOperation = "destination-in";
request.prompt = document.getElementById("prompt").value;
request.negative_prompt = document.getElementById("negPrompt").value;
// Don't allow another image until is finished
blockNewImages = true;
// Get visible pixels
const visibleCanvas = uil.getVisible(bb);
// Use txt2img if canvas is blank
if (isCanvasBlank(0, 0, bb.w, bb.h, visibleCanvas)) {
// Dream
_generate("txt2img", request, bb);
} else {
// Use img2img if not
// Temporary canvas for init image and mask generation
const auxCanvas = document.createElement("canvas");
auxCanvas.width = request.width;
auxCanvas.height = request.height;
const auxCtx = auxCanvas.getContext("2d");
auxCtx.fillStyle = "#000F";
// Get init image
auxCtx.fillRect(0, 0, request.width, request.height);
auxCtx.drawImage( auxCtx.drawImage(
visibleCanvas, visibleCanvas,
0, 0,
@ -474,82 +554,48 @@ const dream_generate_callback = async (evn, state) => {
request.width, request.width,
request.height request.height
); );
request.init_images = [auxCanvas.toDataURL()]; } else {
auxCtx.globalCompositeOperation = "destination-in";
// Get mask image auxCtx.drawImage(
auxCtx.fillStyle = "#000F"; visibleCanvas,
auxCtx.fillRect(0, 0, request.width, request.height); 0,
if (state.invertMask) { 0,
// overmasking by definition is entirely pointless with an inverted mask outpaint bb.w,
// since it should explicitly avoid brushed masks too, we just won't even bother bb.h,
auxCtx.globalCompositeOperation = "destination-in"; 0,
auxCtx.drawImage( 0,
maskPaintCanvas, request.width,
bb.x, request.height
bb.y, );
bb.w, // here's where to overmask to avoid including the brushed mask
bb.h, // 99% of my issues were from failing to set source-over for the overmask blotches
0, if (state.overMaskPx > 0) {
0, // transparent to white first
request.width, auxCtx.globalCompositeOperation = "destination-atop";
request.height auxCtx.fillStyle = "#FFFF";
); auxCtx.fillRect(0, 0, request.width, request.height);
applyOvermask(auxCanvas, auxCtx, state.overMaskPx);
auxCtx.globalCompositeOperation = "destination-in";
auxCtx.drawImage(
visibleCanvas,
0,
0,
bb.w,
bb.h,
0,
0,
request.width,
request.height
);
} else {
auxCtx.globalCompositeOperation = "destination-in";
auxCtx.drawImage(
visibleCanvas,
0,
0,
bb.w,
bb.h,
0,
0,
request.width,
request.height
);
// here's where to overmask to avoid including the brushed mask
// 99% of my issues were from failing to set source-over for the overmask blotches
if (state.overMaskPx > 0) {
// transparent to white first
auxCtx.globalCompositeOperation = "destination-atop";
auxCtx.fillStyle = "#FFFF";
auxCtx.fillRect(0, 0, request.width, request.height);
applyOvermask(auxCanvas, auxCtx, state.overMaskPx);
}
auxCtx.globalCompositeOperation = "destination-out"; // ???
auxCtx.drawImage(
maskPaintCanvas,
bb.x,
bb.y,
bb.w,
bb.h,
0,
0,
request.width,
request.height
);
} }
auxCtx.globalCompositeOperation = "destination-atop";
auxCtx.fillStyle = "#FFFF"; auxCtx.globalCompositeOperation = "destination-out"; // ???
auxCtx.fillRect(0, 0, request.width, request.height); auxCtx.drawImage(
request.mask = auxCanvas.toDataURL(); maskPaintCanvas,
// Dream bb.x,
_generate("img2img", request, bb); bb.y,
bb.w,
bb.h,
0,
0,
request.width,
request.height
);
} }
auxCtx.globalCompositeOperation = "destination-atop";
auxCtx.fillStyle = "#FFFF";
auxCtx.fillRect(0, 0, request.width, request.height);
request.mask = auxCanvas.toDataURL();
// Dream
_generate("img2img", request, bb);
} }
}; };
const dream_erase_callback = (evn, state) => { const dream_erase_callback = (evn, state) => {
@ -606,140 +652,135 @@ function applyOvermask(canvas, ctx, px) {
* Image to Image * Image to Image
*/ */
const dream_img2img_callback = (evn, state) => { const dream_img2img_callback = (evn, state) => {
if (!blockNewImages) { const bb = getBoundingBox(
const bb = getBoundingBox( evn.x,
evn.x, evn.y,
evn.y, state.cursorSize,
state.cursorSize, state.cursorSize,
state.cursorSize, state.snapToGrid && basePixelCount
state.snapToGrid && basePixelCount );
);
// Get visible pixels // Get visible pixels
const visibleCanvas = uil.getVisible(bb); const visibleCanvas = uil.getVisible(bb);
// Do nothing if no image exists // Do nothing if no image exists
if (isCanvasBlank(0, 0, bb.w, bb.h, visibleCanvas)) return; if (isCanvasBlank(0, 0, bb.w, bb.h, visibleCanvas)) return;
// Build request to the API // Build request to the API
const request = {}; const request = {};
Object.assign(request, stableDiffusionData); Object.assign(request, stableDiffusionData);
request.denoising_strength = state.denoisingStrength; request.denoising_strength = state.denoisingStrength;
request.inpainting_fill = 1; // For img2img use original request.inpainting_fill = 1; // For img2img use original
// Load prompt (maybe we should add some events so we don't have to do this) // Load prompt (maybe we should add some events so we don't have to do this)
request.prompt = document.getElementById("prompt").value; request.prompt = document.getElementById("prompt").value;
request.negative_prompt = document.getElementById("negPrompt").value; request.negative_prompt = document.getElementById("negPrompt").value;
// Don't allow another image until is finished // Use img2img
blockNewImages = true;
// Use img2img // Temporary canvas for init image and mask generation
const auxCanvas = document.createElement("canvas");
auxCanvas.width = request.width;
auxCanvas.height = request.height;
const auxCtx = auxCanvas.getContext("2d");
// Temporary canvas for init image and mask generation auxCtx.fillStyle = "#000F";
const auxCanvas = document.createElement("canvas");
auxCanvas.width = request.width;
auxCanvas.height = request.height;
const auxCtx = auxCanvas.getContext("2d");
// Get init image
auxCtx.fillRect(0, 0, request.width, request.height);
auxCtx.drawImage(
visibleCanvas,
0,
0,
bb.w,
bb.h,
0,
0,
request.width,
request.height
);
request.init_images = [auxCanvas.toDataURL()];
// Get mask image
auxCtx.fillStyle = state.invertMask ? "#FFFF" : "#000F";
auxCtx.fillRect(0, 0, request.width, request.height);
auxCtx.globalCompositeOperation = "destination-out";
auxCtx.drawImage(
maskPaintCanvas,
bb.x,
bb.y,
bb.w,
bb.h,
0,
0,
request.width,
request.height
);
auxCtx.globalCompositeOperation = "destination-atop";
auxCtx.fillStyle = state.invertMask ? "#000F" : "#FFFF";
auxCtx.fillRect(0, 0, request.width, request.height);
// Border Mask
if (state.keepBorderSize > 0) {
auxCtx.globalCompositeOperation = "source-over";
auxCtx.fillStyle = "#000F"; auxCtx.fillStyle = "#000F";
if (state.gradient) {
// Get init image const lg = auxCtx.createLinearGradient(0, 0, state.keepBorderSize, 0);
auxCtx.fillRect(0, 0, request.width, request.height); lg.addColorStop(0, "#000F");
auxCtx.drawImage( lg.addColorStop(1, "#0000");
visibleCanvas, auxCtx.fillStyle = lg;
0,
0,
bb.w,
bb.h,
0,
0,
request.width,
request.height
);
request.init_images = [auxCanvas.toDataURL()];
// Get mask image
auxCtx.fillStyle = state.invertMask ? "#FFFF" : "#000F";
auxCtx.fillRect(0, 0, request.width, request.height);
auxCtx.globalCompositeOperation = "destination-out";
auxCtx.drawImage(
maskPaintCanvas,
bb.x,
bb.y,
bb.w,
bb.h,
0,
0,
request.width,
request.height
);
auxCtx.globalCompositeOperation = "destination-atop";
auxCtx.fillStyle = state.invertMask ? "#000F" : "#FFFF";
auxCtx.fillRect(0, 0, request.width, request.height);
// Border Mask
if (state.keepBorderSize > 0) {
auxCtx.globalCompositeOperation = "source-over";
auxCtx.fillStyle = "#000F";
if (state.gradient) {
const lg = auxCtx.createLinearGradient(0, 0, state.keepBorderSize, 0);
lg.addColorStop(0, "#000F");
lg.addColorStop(1, "#0000");
auxCtx.fillStyle = lg;
}
auxCtx.fillRect(0, 0, state.keepBorderSize, request.height);
if (state.gradient) {
const tg = auxCtx.createLinearGradient(0, 0, 0, state.keepBorderSize);
tg.addColorStop(0, "#000F");
tg.addColorStop(1, "#0000");
auxCtx.fillStyle = tg;
}
auxCtx.fillRect(0, 0, request.width, state.keepBorderSize);
if (state.gradient) {
const rg = auxCtx.createLinearGradient(
request.width,
0,
request.width - state.keepBorderSize,
0
);
rg.addColorStop(0, "#000F");
rg.addColorStop(1, "#0000");
auxCtx.fillStyle = rg;
}
auxCtx.fillRect(
request.width - state.keepBorderSize,
0,
state.keepBorderSize,
request.height
);
if (state.gradient) {
const bg = auxCtx.createLinearGradient(
0,
request.height,
0,
request.height - state.keepBorderSize
);
bg.addColorStop(0, "#000F");
bg.addColorStop(1, "#0000");
auxCtx.fillStyle = bg;
}
auxCtx.fillRect(
0,
request.height - state.keepBorderSize,
request.width,
state.keepBorderSize
);
} }
auxCtx.fillRect(0, 0, state.keepBorderSize, request.height);
request.mask = auxCanvas.toDataURL(); if (state.gradient) {
request.inpaint_full_res = state.fullResolution; const tg = auxCtx.createLinearGradient(0, 0, 0, state.keepBorderSize);
tg.addColorStop(0, "#000F");
// Dream tg.addColorStop(1, "#0000");
_generate("img2img", request, bb); auxCtx.fillStyle = tg;
}
auxCtx.fillRect(0, 0, request.width, state.keepBorderSize);
if (state.gradient) {
const rg = auxCtx.createLinearGradient(
request.width,
0,
request.width - state.keepBorderSize,
0
);
rg.addColorStop(0, "#000F");
rg.addColorStop(1, "#0000");
auxCtx.fillStyle = rg;
}
auxCtx.fillRect(
request.width - state.keepBorderSize,
0,
state.keepBorderSize,
request.height
);
if (state.gradient) {
const bg = auxCtx.createLinearGradient(
0,
request.height,
0,
request.height - state.keepBorderSize
);
bg.addColorStop(0, "#000F");
bg.addColorStop(1, "#0000");
auxCtx.fillStyle = bg;
}
auxCtx.fillRect(
0,
request.height - state.keepBorderSize,
request.width,
state.keepBorderSize
);
} }
request.mask = auxCanvas.toDataURL();
request.inpaint_full_res = state.fullResolution;
// Dream
_generate("img2img", request, bb);
}; };
/** /**

View file

@ -181,6 +181,7 @@ const stampTool = () =>
saveButton.addEventListener( saveButton.addEventListener(
"click", "click",
(evn) => { (evn) => {
evn.stopPropagation();
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
canvas.width = resource.image.width; canvas.width = resource.image.width;
canvas.height = resource.image.height; canvas.height = resource.image.height;