diff --git a/README.md b/README.md
index a1c3041..15a2fa7 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@ this is a completely vanilla javascript and html canvas outpainting convenience
- queueable, cancelable dreams - just start a'clickin' all over the place
- arbitrary dream reticle size - draw the rectangle of your dreams
- an [effectively infinite](https://github.com/zero01101/openOutpaint/pull/108), resizable, scalable canvas for you to paint all over
- - **_NOTE: v0.0.10 introduces a new "camera control" modifier key - hold [`CTRL`] ([`CMD`] on mac) and use the scrollwheel to zoom (scroll the wheel) and pan (hold the wheel button) around the canvas_**
+ - **_NOTE: v0.0.10 introduces a new "camera control" modifier key - hold [`CTRL`] and use the scrollwheel to zoom (scroll the wheel or use the two-finger vertical gesture on, uh, modern touchpads) and pan (hold the scrollwheel button, or if you don't have one, left-click button) around the canvas_**
- a very nicely functional and familiar layer system
- inpainting/touchup mask brush
- prompt history panel
@@ -36,12 +36,14 @@ this is a completely vanilla javascript and html canvas outpainting convenience
- floating toolbox with handy keyboard shortcuts
- optional grid snapping for precision
- optional hi-res fix for blank/txt2img dreams which, if enabled, uses image width/height / 2 as firstpass size
+ - optional HRfix lock px to constrain maximum firstpass values
- optional overmasking for potentially better seams between outpaints - set overmask px value to 0 to disable the feature
- import arbitrary images and scale/stamp on the canvas whenever, wherever you'd like
- upscaler support for final output images
- saves your preferences/imported images to browser localstorage for maximum convenience
- reset to defaults button to unsave your preferences if things go squirrely
- floating navigable undo/redo palette with ctrl+z/y keyboard shortcuts for additional maximum convenience and desquirreliness
+- optional generate-ahead function to keep crankin' out the dreams while you look through the ones that already exist
- _all this and much more for the low, low price of simply already owning an expensive GPU!_
## operation
diff --git a/css/index.css b/css/index.css
index a39c2c6..80fa23d 100644
--- a/css/index.css
+++ b/css/index.css
@@ -304,6 +304,15 @@ input#host {
box-sizing: border-box;
}
+/* Model Select */
+#models-ac-select option {
+ background-color: #fcc;
+}
+
+#models-ac-select option.inpainting {
+ background-color: #cfc;
+}
+
/* Settings button */
.ui.icon.header-button {
padding: 0;
diff --git a/css/ui/generic.css b/css/ui/generic.css
index ca8f67f..93caf35 100644
--- a/css/ui/generic.css
+++ b/css/ui/generic.css
@@ -100,6 +100,26 @@ div.slider-wrapper > input.text {
background-color: transparent;
}
+/* Bare Select */
+
+.bareselector {
+ border-radius: 5px;
+
+ background-color: white;
+
+ overflow-y: auto;
+
+ margin-top: 0;
+ margin-left: 0;
+
+ max-height: 200px;
+ min-width: 100%;
+ max-width: 800px;
+
+ width: fit-content;
+ z-index: 200;
+}
+
/* Autocomplete Select */
div.autocomplete {
border-radius: 5px;
diff --git a/index.html b/index.html
index 5871f01..857556c 100644
--- a/index.html
+++ b/index.html
@@ -7,10 +7,10 @@
-
+
-
+
@@ -100,6 +100,7 @@
Auto txt2img HRfix
+
- Alpha release v0.0.12.1
+ Alpha release v0.0.12.3
@@ -310,7 +311,7 @@
+ src="pages/configuration.html?v=ae8af5d">
@@ -324,8 +325,8 @@
-
-
+
+
-
+
-
+
diff --git a/js/index.js b/js/index.js
index 133439f..fb68673 100644
--- a/js/index.js
+++ b/js/index.js
@@ -531,6 +531,16 @@ const modelAutoComplete = createAutoComplete(
"Model",
document.getElementById("models-ac-select")
);
+modelAutoComplete.onchange.on(({value}) => {
+ if (value.toLowerCase().includes("inpainting"))
+ document.querySelector(
+ "#models-ac-select input.autocomplete-text"
+ ).style.backgroundColor = "#cfc";
+ else
+ document.querySelector(
+ "#models-ac-select input.autocomplete-text"
+ ).style.backgroundColor = "#fcc";
+});
const samplerAutoComplete = createAutoComplete(
"Sampler",
@@ -565,8 +575,8 @@ makeSlider(
"CFG Scale",
document.getElementById("cfgScale"),
"cfg_scale",
- -1,
- 25,
+ localStorage.getItem("openoutpaint/settings.min-cfg") || 1,
+ localStorage.getItem("openoutpaint/settings.max-cfg") || 25,
0.5,
7.0,
0.1
@@ -600,7 +610,27 @@ makeSlider(
0.1
);
-makeSlider("Steps", document.getElementById("steps"), "steps", 1, 70, 5, 30, 1);
+makeSlider(
+ "Steps",
+ document.getElementById("steps"),
+ "steps",
+ 1,
+ localStorage.getItem("openoutpaint/settings.max-steps") || 70,
+ 5,
+ 30,
+ 1
+);
+
+makeSlider(
+ "HRfix Lock Px.",
+ document.getElementById("hrFixLock"),
+ "hr_fix_lock_px",
+ 0.0,
+ 768.0,
+ 256.0,
+ 0.0,
+ 1.0
+);
function changeMaskBlur() {
stableDiffusionData.mask_blur = parseInt(
@@ -782,6 +812,10 @@ async function getModels() {
modelAutoComplete.options = data.map((option) => ({
name: option.title,
value: option.title,
+ optionelcb: (el) => {
+ if (option.title.toLowerCase().includes("inpainting"))
+ el.classList.add("inpainting");
+ },
}));
try {
diff --git a/js/lib/toolbar.js b/js/lib/toolbar.js
index d8cad9b..232b0fe 100644
--- a/js/lib/toolbar.js
+++ b/js/lib/toolbar.js
@@ -188,4 +188,31 @@ const _toolbar_input = {
},
};
},
+
+ selectlist: (
+ state,
+ dataKey,
+ text,
+ options = {value, text},
+ defaultOptionValue,
+ cb = null
+ ) => {
+ const selectlist = document.createElement("select");
+ selectlist.classList.add("bareselector");
+ Object.entries(options).forEach((opt) => {
+ var option = document.createElement("option");
+ option.value = opt[0];
+ option.text = opt[1];
+ selectlist.options.add(option);
+ });
+ selectlist.selectedIndex = defaultOptionValue;
+ selectlist.onchange = () => {
+ state[dataKey] = selectlist.selectedIndex;
+ cb && cb();
+ };
+ const label = document.createElement("label");
+ label.appendChild(new Text(text));
+ label.appendChild(selectlist);
+ return {selectlist, label};
+ },
};
diff --git a/js/lib/ui.js b/js/lib/ui.js
index 5e4cd22..e0d7598 100644
--- a/js/lib/ui.js
+++ b/js/lib/ui.js
@@ -206,7 +206,7 @@ function createSlider(name, wrapper, options = {}) {
* @param {HTMLDivElement} wrapper The div element that will wrap the input elements
* @param {object} options Extra options
* @param {boolean} options.multiple Whether multiple options can be selected
- * @param {{name: string, value: string}[]} options.options Options to add to the selector
+ * @param {{name: string, value: string, optionelcb: (el: HTMLOptionElement) => void}[]} options.options Options to add to the selector
* @returns {AutoCompleteElement}
*/
function createAutoComplete(name, wrapper, options = {}) {
@@ -293,6 +293,7 @@ function createAutoComplete(name, wrapper, options = {}) {
const optionEl = document.createElement("option");
optionEl.classList.add("autocomplete-option");
optionEl.title = title || name;
+ if (opt.optionelcb) opt.optionelcb(optionEl);
const option = {name, value, optionElement: optionEl};
diff --git a/js/ui/tool/dream.js b/js/ui/tool/dream.js
index adb6224..f0e1fab 100644
--- a/js/ui/tool/dream.js
+++ b/js/ui/tool/dream.js
@@ -375,12 +375,16 @@ const _generate = async (endpoint, request, bb, options = {}) => {
});
};
+ const sendInterrupt = () => {
+ fetch(`${host}${config.api.path}interrupt`, {method: "POST"});
+ };
+
// Add Interrupt Button
const interruptButton = makeElement("button", bb.x + bb.w - 100, bb.y + bb.h);
interruptButton.classList.add("dream-stop-btn");
interruptButton.textContent = "Interrupt";
interruptButton.addEventListener("click", () => {
- fetch(`${host}${config.api.path}interrupt`, {method: "POST"});
+ sendInterrupt();
interruptButton.disabled = true;
});
const marchingOptions = {};
@@ -390,6 +394,9 @@ const _generate = async (endpoint, request, bb, options = {}) => {
console.info(`[dream] Generating images for prompt '${request.prompt}'`);
console.debug(request);
+ eagerGenerateCount = toolbar._current_tool.state.eagerGenerateCount;
+ isDreamComplete = false;
+
let stopProgress = null;
try {
let stopDrawingStatus = false;
@@ -428,6 +435,19 @@ const _generate = async (endpoint, request, bb, options = {}) => {
imageCollection.inputElement.removeChild(interruptButton);
}
+ const needMoreGenerations = () => {
+ return (
+ eagerGenerateCount > 0 &&
+ images.length - highestNavigatedImageIndex <= eagerGenerateCount
+ );
+ };
+
+ const isGenerationPending = () => {
+ return generationQueue.length > 0;
+ };
+
+ let highestNavigatedImageIndex = 0;
+
// Image navigation
const prevImg = () => {
at--;
@@ -443,10 +463,16 @@ const _generate = async (endpoint, request, bb, options = {}) => {
at++;
if (at >= images.length) at = 0;
+ highestNavigatedImageIndex = Math.max(at, highestNavigatedImageIndex);
+
imageindextxt.textContent = `${at}/${images.length - 1}`;
var seed = seeds[at];
seedbtn.title = "Use seed " + seed;
redraw();
+
+ if (needMoreGenerations() && !isGenerationPending()) {
+ makeMore();
+ }
};
const applyImg = async () => {
@@ -504,6 +530,11 @@ const _generate = async (endpoint, request, bb, options = {}) => {
}
nextQueue(moreQ);
+
+ //Start the next batch if we're eager-generating
+ if (needMoreGenerations() && !isGenerationPending() && !isDreamComplete) {
+ makeMore();
+ }
};
const discardImg = async () => {
@@ -657,6 +688,10 @@ const _generate = async (endpoint, request, bb, options = {}) => {
mouse.listen.world.btn.right.onclick.clear(oncancelhandler);
mouse.listen.world.btn.middle.onclick.clear(onmorehandler);
mouse.listen.world.onwheel.clear(onwheelhandler);
+ isDreamComplete = true;
+ if (generating) {
+ sendInterrupt();
+ }
};
redraw();
@@ -740,6 +775,11 @@ const _generate = async (endpoint, request, bb, options = {}) => {
imageSelectMenu.appendChild(seedbtn);
nextQueue(initialQ);
+
+ //Start the next batch after the initial generation
+ if (needMoreGenerations()) {
+ makeMore();
+ }
};
/**
@@ -932,7 +972,7 @@ const dream_img2img_callback = (bb, resolution, state) => {
request.height = resolution.h;
request.denoising_strength = state.denoisingStrength;
- request.inpainting_fill = 1; // For img2img use original
+ request.inpainting_fill = state.inpainting_fill; //let's see how this works //1; // For img2img use original
// Load prompt (maybe we should add some events so we don't have to do this)
request.prompt = document.getElementById("prompt").value;
@@ -1186,6 +1226,7 @@ const dreamTool = () =>
state.keepUnmaskedBlur = 8;
state.overMaskPx = 20;
state.preserveMasks = false;
+ state.eagerGenerateCount = 0;
state.erasePrevCursor = () =>
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
@@ -1346,6 +1387,18 @@ const dreamTool = () =>
h: stableDiffusionData.height,
};
+ //hacky set non-square auto hrfix values
+ let hrLockPx =
+ localStorage.getItem("openoutpaint/hr_fix_lock_px") ?? 0;
+ stableDiffusionData.firstphase_height =
+ hrLockPx == 0 || resolution.h / 2 <= hrLockPx
+ ? resolution.h / 2
+ : hrLockPx;
+ stableDiffusionData.firstphase_width =
+ hrLockPx == 0 || resolution.w / 2 <= hrLockPx
+ ? resolution.w / 2
+ : hrLockPx;
+
if (global.connection === "online") {
dream_generate_callback(bb, resolution, state);
} else {
@@ -1477,6 +1530,19 @@ const dreamTool = () =>
textStep: 1,
}
).slider;
+
+ // Eager generation Slider
+ state.ctxmenu.eagerGenerateCountLabel = _toolbar_input.slider(
+ state,
+ "eagerGenerateCount",
+ "Generate-ahead count",
+ {
+ min: 0,
+ max: 100,
+ step: 2,
+ textStep: 1,
+ }
+ ).slider;
}
menu.appendChild(state.ctxmenu.cursorSizeSlider);
@@ -1489,6 +1555,7 @@ const dreamTool = () =>
menu.appendChild(state.ctxmenu.preserveMasksLabel);
menu.appendChild(document.createElement("br"));
menu.appendChild(state.ctxmenu.overMaskPxLabel);
+ menu.appendChild(state.ctxmenu.eagerGenerateCountLabel);
},
shortcut: "D",
}
@@ -1573,6 +1640,7 @@ const img2imgTool = () =>
state.keepUnmaskedBlur = 8;
state.fullResolution = false;
state.preserveMasks = false;
+ state.eagerGenerateCount = 0;
state.denoisingStrength = 0.7;
@@ -2006,6 +2074,36 @@ const img2imgTool = () =>
textStep: 1,
}
).slider;
+
+ // inpaint fill type select list
+ state.ctxmenu.inpaintTypeSelect = _toolbar_input.selectlist(
+ state,
+ "inpainting_fill",
+ "Inpaint Type",
+ {
+ 0: "fill",
+ 1: "original (recommended)",
+ 2: "latent noise",
+ 3: "latent nothing",
+ },
+ 1, // USE ORIGINAL FOR IMG2IMG OR ELSE but we still give you the option because we love you
+ () => {
+ stableDiffusionData.inpainting_fill = state.inpainting_fill;
+ }
+ ).label;
+
+ // Eager generation Slider
+ state.ctxmenu.eagerGenerateCountLabel = _toolbar_input.slider(
+ state,
+ "eagerGenerateCount",
+ "Generate-ahead count",
+ {
+ min: 0,
+ max: 100,
+ step: 2,
+ textStep: 1,
+ }
+ ).slider;
}
menu.appendChild(state.ctxmenu.cursorSizeSlider);
@@ -2020,9 +2118,11 @@ const img2imgTool = () =>
menu.appendChild(document.createElement("br"));
menu.appendChild(state.ctxmenu.fullResolutionLabel);
menu.appendChild(document.createElement("br"));
+ menu.appendChild(state.ctxmenu.inpaintTypeSelect);
menu.appendChild(state.ctxmenu.denoisingStrengthSlider);
menu.appendChild(state.ctxmenu.borderMaskGradientCheckbox);
menu.appendChild(state.ctxmenu.borderMaskSlider);
+ menu.appendChild(state.ctxmenu.eagerGenerateCountLabel);
},
shortcut: "I",
}
@@ -2030,8 +2130,7 @@ const img2imgTool = () =>
window.onbeforeunload = async () => {
// Stop current generation on page close
- if (generating)
- await fetch(`${host}${config.api.path}interrupt`, {method: "POST"});
+ if (generating) await sendInterrupt();
};
const sendSeed = (seed) => {
diff --git a/pages/configuration.html b/pages/configuration.html
index 313a527..2adff2a 100644
--- a/pages/configuration.html
+++ b/pages/configuration.html
@@ -7,10 +7,10 @@
-
+
-
+
@@ -59,10 +59,39 @@
type="number"
step="1" />
+
+ Max Steps:
+
+
+
+ CFG minmax:
+
+ ::
+
+
+ Refresh the page to apply settings.