diff --git a/css/icons.css b/css/icons.css
index a3b8ca9..e808b3b 100644
--- a/css/icons.css
+++ b/css/icons.css
@@ -1,3 +1,34 @@
+.ui.inline-icon {
+ position: relative;
+
+ display: flex;
+}
+
+.ui.inline-icon::after {
+ content: "";
+ display: block;
+
+ position: absolute;
+
+ box-sizing: border-box;
+
+ margin: auto;
+ top: 15%;
+ bottom: 15%;
+
+ mask-size: contain;
+ mask-repeat: no-repeat;
+
+ max-height: 70%;
+ aspect-ratio: 1;
+
+ left: 0;
+ right: 0;
+
+ background-color: var(--c-text);
+}
+
+.ui.inline-icon.icon-eye-off::after,
.ui.icon > .icon-eye-off {
-webkit-mask-image: url("../res/icons/eye-off.svg");
mask-image: url("../res/icons/eye-off.svg");
@@ -42,3 +73,57 @@
-webkit-mask-image: url("../res/icons/settings.svg");
mask-image: url("../res/icons/settings.svg");
}
+
+.ui.inline-icon.icon-grid::after,
+.ui.icon > .icon-grid {
+ -webkit-mask-image: url("../res/icons/grid.svg");
+ mask-image: url("../res/icons/grid.svg");
+}
+
+.ui.inline-icon.icon-venetian-mask::after,
+.ui.icon > .icon-venetian-mask {
+ -webkit-mask-image: url("../res/icons/venetian-mask.svg");
+ mask-image: url("../res/icons/venetian-mask.svg");
+}
+
+.ui.inline-icon.icon-brush::after,
+.ui.icon > .icon-brush {
+ -webkit-mask-image: url("../res/icons/brush.svg");
+ mask-image: url("../res/icons/brush.svg");
+}
+
+.ui.inline-icon.icon-paintbrush::after,
+.ui.icon > .icon-paintbrush {
+ -webkit-mask-image: url("../res/icons/paintbrush.svg");
+ mask-image: url("../res/icons/paintbrush.svg");
+}
+
+.ui.inline-icon.icon-expand::after,
+.ui.icon > .icon-expand {
+ -webkit-mask-image: url("../res/icons/expand.svg");
+ mask-image: url("../res/icons/expand.svg");
+}
+
+.ui.inline-icon.icon-pin::after,
+.ui.icon > .icon-pin {
+ -webkit-mask-image: url("../res/icons/pin.svg");
+ mask-image: url("../res/icons/pin.svg");
+}
+
+.ui.inline-icon.icon-box-select::after,
+.ui.icon > .icon-box-select {
+ -webkit-mask-image: url("../res/icons/box-select.svg");
+ mask-image: url("../res/icons/box-select.svg");
+}
+
+.ui.inline-icon.icon-maximize::after,
+.ui.icon > .icon-maximize {
+ -webkit-mask-image: url("../res/icons/maximize.svg");
+ mask-image: url("../res/icons/maximize.svg");
+}
+
+.ui.inline-icon.icon-clipboard-list::after,
+.ui.icon > .icon-clipboard-list {
+ -webkit-mask-image: url("../res/icons/clipboard-list.svg");
+ mask-image: url("../res/icons/clipboard-list.svg");
+}
diff --git a/css/ui/generic.css b/css/ui/generic.css
index 93caf35..801e2ba 100644
--- a/css/ui/generic.css
+++ b/css/ui/generic.css
@@ -37,6 +37,8 @@
/* Slider Input */
div.slider-wrapper {
margin: 5px;
+ margin-left: 0;
+ margin-right: 0;
}
div.slider-wrapper {
@@ -100,6 +102,83 @@ div.slider-wrapper > input.text {
background-color: transparent;
}
+/* Checkbox Input */
+div.checkbox-array {
+ display: flex;
+
+ margin-top: 5px;
+ margin-bottom: 5px;
+}
+
+input.oo-checkbox[type="checkbox"] {
+ /* Hide original checkbox */
+ -webkit-appearance: none;
+ appearance: none;
+
+ flex: 1;
+
+ margin: 0;
+
+ min-width: 28px;
+ height: 28px;
+
+ background-color: var(--c-primary);
+
+ cursor: pointer;
+}
+
+input.oo-checkbox[type="checkbox"]:disabled {
+ background-color: #666 !important;
+ cursor: default !important;
+}
+
+input.oo-checkbox[type="checkbox"]:disabled:hover {
+ filter: none !important;
+}
+
+input.oo-checkbox[type="checkbox"]:checked::after {
+ background-color: #66f;
+}
+
+input.oo-checkbox[type="checkbox"]:hover {
+ background-color: var(--c-hover);
+}
+
+input.oo-checkbox[type="checkbox"]:active {
+ filter: brightness(120%);
+}
+
+input.oo-checkbox[type="checkbox"]:first-child {
+ border-top-left-radius: 5px;
+ border-bottom-left-radius: 5px;
+}
+
+input.oo-checkbox[type="checkbox"]:last-child {
+ border-top-right-radius: 5px;
+ border-bottom-right-radius: 5px;
+}
+
+/* Mask Inversion Checkbox */
+input.oo-checkbox[type="checkbox"].invert-mask-checkbox::after {
+ background-color: var(--c-text);
+}
+
+input.oo-checkbox[type="checkbox"].invert-mask-checkbox:hover {
+ filter: brightness(120%);
+}
+
+input.oo-checkbox[type="checkbox"].invert-mask-checkbox:active {
+ filter: brightness(140%);
+}
+
+input.oo-checkbox[type="checkbox"].invert-mask-checkbox {
+ background-color: #922;
+}
+
+input.oo-checkbox[type="checkbox"].invert-mask-checkbox:checked {
+ background-color: #229;
+}
+
/* Bare Select */
.bareselector {
diff --git a/index.html b/index.html
index c9d9462..b2bef61 100644
--- a/index.html
+++ b/index.html
@@ -5,12 +5,12 @@
openOutpaint 🐠
-
+
-
+
@@ -319,7 +319,7 @@
-
+
@@ -328,7 +328,7 @@
-
+
-
+
-
+
-
+
diff --git a/js/global.js b/js/global.js
index 19a3542..72cb565 100644
--- a/js/global.js
+++ b/js/global.js
@@ -3,6 +3,11 @@
*/
const global = {
+ // If this is the first run of openOutpaint
+ get firstRun() {
+ return this._firstRun;
+ },
+
// Connection
_connection: "offline",
set connection(v) {
@@ -46,3 +51,5 @@ const global = {
this.debug = !this.debug;
},
};
+
+global._firstRun = !localStorage.getItem("openoutpaint/host");
diff --git a/js/index.js b/js/index.js
index ab002f0..f950f1f 100644
--- a/js/index.js
+++ b/js/index.js
@@ -139,7 +139,6 @@ var host = "";
var url = "/sdapi/v1/";
const basePixelCount = 64; //64 px - ALWAYS 64 PX
-//
function startup() {
testHostConfiguration();
loadSettings();
@@ -857,12 +856,14 @@ async function getUpscalers() {
}
async function getModels() {
- var url = document.getElementById("host").value + "/sdapi/v1/sd-models";
+ const url = document.getElementById("host").value + "/sdapi/v1/sd-models";
+ let opt = null;
+
try {
const response = await fetch(url);
const data = await response.json();
- modelAutoComplete.options = data.map((option) => ({
+ opt = data.map((option) => ({
name: option.title,
value: option.title,
optionelcb: (el) => {
@@ -871,6 +872,8 @@ async function getModels() {
},
}));
+ modelAutoComplete.options = opt;
+
try {
const optResponse = await fetch(
document.getElementById("host").value + "/sdapi/v1/options"
@@ -891,10 +894,10 @@ async function getModels() {
modelAutoComplete.onchange.on(async ({value}) => {
console.log(`[index] Changing model to [${value}]`);
- var payload = {
+ const payload = {
sd_model_checkpoint: value,
};
- var url = document.getElementById("host").value + "/sdapi/v1/options/";
+ const url = document.getElementById("host").value + "/sdapi/v1/options/";
try {
await fetch(url, {
method: "POST",
@@ -914,6 +917,25 @@ async function getModels() {
);
}
});
+
+ // If first time running, ask if user wants to switch to an inpainting model
+ if (global.firstRun && !modelAutoComplete.value.includes("inpainting")) {
+ const inpainting = opt.find(({name}) => name.includes("inpainting"));
+
+ let message =
+ "It seems this is your first time using openOutpaint. It is highly recommended that you switch to an inpainting model. \
+ These are highlighted as green in the model selector.";
+
+ if (inpainting) {
+ message += `\n\nWe have found the inpainting model\n\n - ${inpainting.name}\n\navailable in the webui. Do you want to switch to it?`;
+ if (confirm(message)) {
+ modelAutoComplete.value = inpainting.value;
+ }
+ } else {
+ message += `\n\nNo inpainting model seems to be available in the webui. It is recommended that you download an inpainting model, or outpainting results may not be optimal.`;
+ alert(message);
+ }
+ }
}
async function getConfig() {
diff --git a/js/lib/toolbar.js b/js/lib/toolbar.js
index 232b0fe..10e48f8 100644
--- a/js/lib/toolbar.js
+++ b/js/lib/toolbar.js
@@ -145,17 +145,24 @@ const toolbar = {
* Premade inputs for populating the context menus
*/
const _toolbar_input = {
- checkbox: (state, dataKey, text, cb = null) => {
+ checkbox: (state, dataKey, text, classes, cb = null) => {
if (state[dataKey] === undefined) state[dataKey] = false;
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
+ checkbox.classList.add("oo-checkbox", "ui", "inline-icon");
+
+ if (typeof classes === "string") classes = [classes];
+
+ if (classes) checkbox.classList.add(...classes);
checkbox.checked = state[dataKey];
checkbox.onchange = () => {
state[dataKey] = checkbox.checked;
cb && cb();
};
+ checkbox.title = text;
+
const label = document.createElement("label");
label.appendChild(checkbox);
label.appendChild(new Text(text));
diff --git a/js/ui/tool/colorbrush.js b/js/ui/tool/colorbrush.js
index 5786307..dc1d190 100644
--- a/js/ui/tool/colorbrush.js
+++ b/js/ui/tool/colorbrush.js
@@ -349,13 +349,16 @@ const colorBrushTool = () =>
state.ctxmenu = {};
// Affects Mask Checkbox
+ const array = document.createElement("div");
const affectMaskCheckbox = _toolbar_input.checkbox(
state,
"affectMask",
- "Affect Mask"
- ).label;
+ "Affect Mask",
+ "icon-venetian-mask"
+ ).checkbox;
+ array.appendChild(affectMaskCheckbox);
- state.ctxmenu.affectMaskCheckbox = affectMaskCheckbox;
+ state.ctxmenu.affectMaskCheckbox = array;
// Brush size slider
const brushSizeSlider = _toolbar_input.slider(
diff --git a/js/ui/tool/dream.js b/js/ui/tool/dream.js
index 53428a1..18e42ad 100644
--- a/js/ui/tool/dream.js
+++ b/js/ui/tool/dream.js
@@ -1473,24 +1473,27 @@ const dreamTool = () =>
state.ctxmenu.snapToGridLabel = _toolbar_input.checkbox(
state,
"snapToGrid",
- "Snap To Grid"
- ).label;
+ "Snap To Grid",
+ "icon-grid"
+ ).checkbox;
// Invert Mask Checkbox
state.ctxmenu.invertMaskLabel = _toolbar_input.checkbox(
state,
"invertMask",
"Invert Mask",
+ ["icon-venetian-mask", "invert-mask-checkbox"],
() => {
setMask(state.invertMask ? "hold" : "clear");
}
- ).label;
+ ).checkbox;
// Keep Masked Content Checkbox
state.ctxmenu.keepUnmaskedLabel = _toolbar_input.checkbox(
state,
"keepUnmasked",
"Keep Unmasked",
+ "icon-pin",
() => {
if (state.keepUnmasked) {
state.ctxmenu.keepUnmaskedBlurSlider.classList.remove(
@@ -1506,7 +1509,7 @@ const dreamTool = () =>
);
}
}
- ).label;
+ ).checkbox;
// Keep Masked Content Blur Slider
state.ctxmenu.keepUnmaskedBlurSlider = _toolbar_input.slider(
@@ -1531,8 +1534,9 @@ const dreamTool = () =>
state.ctxmenu.preserveMasksLabel = _toolbar_input.checkbox(
state,
"preserveMasks",
- "Preserve Brushed Masks"
- ).label;
+ "Preserve Brushed Masks",
+ "icon-paintbrush"
+ ).checkbox;
// Overmasking Slider
state.ctxmenu.overMaskPxLabel = _toolbar_input.slider(
@@ -1562,15 +1566,19 @@ const dreamTool = () =>
}
menu.appendChild(state.ctxmenu.cursorSizeSlider);
- menu.appendChild(state.ctxmenu.snapToGridLabel);
- menu.appendChild(document.createElement("br"));
- menu.appendChild(state.ctxmenu.invertMaskLabel);
- menu.appendChild(document.createElement("br"));
- menu.appendChild(state.ctxmenu.keepUnmaskedLabel);
+ const array = document.createElement("div");
+ array.classList.add("checkbox-array");
+ array.appendChild(state.ctxmenu.snapToGridLabel);
+ //menu.appendChild(document.createElement("br"));
+ array.appendChild(state.ctxmenu.invertMaskLabel);
+ array.appendChild(state.ctxmenu.preserveMasksLabel);
+ //menu.appendChild(document.createElement("br"));
+ array.appendChild(state.ctxmenu.keepUnmaskedLabel);
+ menu.appendChild(array);
menu.appendChild(state.ctxmenu.keepUnmaskedBlurSlider);
- menu.appendChild(state.ctxmenu.keepUnmaskedBlurSliderLinebreak);
- menu.appendChild(state.ctxmenu.preserveMasksLabel);
- menu.appendChild(document.createElement("br"));
+ // menu.appendChild(state.ctxmenu.keepUnmaskedBlurSliderLinebreak);
+ // menu.appendChild(state.ctxmenu.preserveMasksLabel);
+ // menu.appendChild(document.createElement("br"));
menu.appendChild(state.ctxmenu.overMaskPxLabel);
menu.appendChild(state.ctxmenu.eagerGenerateCountLabel);
},
@@ -1991,24 +1999,27 @@ const img2imgTool = () =>
state.ctxmenu.snapToGridLabel = _toolbar_input.checkbox(
state,
"snapToGrid",
- "Snap To Grid"
- ).label;
+ "Snap To Grid",
+ "icon-grid"
+ ).checkbox;
// Invert Mask Checkbox
state.ctxmenu.invertMaskLabel = _toolbar_input.checkbox(
state,
"invertMask",
"Invert Mask",
+ ["icon-venetian-mask", "invert-mask-checkbox"],
() => {
setMask(state.invertMask ? "hold" : "clear");
}
- ).label;
+ ).checkbox;
// Keep Masked Content Checkbox
state.ctxmenu.keepUnmaskedLabel = _toolbar_input.checkbox(
state,
"keepUnmasked",
"Keep Unmasked",
+ "icon-pin",
() => {
if (state.keepUnmasked) {
state.ctxmenu.keepUnmaskedBlurSlider.classList.remove(
@@ -2024,7 +2035,7 @@ const img2imgTool = () =>
);
}
}
- ).label;
+ ).checkbox;
// Keep Masked Content Blur Slider
state.ctxmenu.keepUnmaskedBlurSlider = _toolbar_input.slider(
@@ -2049,15 +2060,17 @@ const img2imgTool = () =>
state.ctxmenu.preserveMasksLabel = _toolbar_input.checkbox(
state,
"preserveMasks",
- "Preserve Brushed Masks"
- ).label;
+ "Preserve Brushed Masks",
+ "icon-paintbrush"
+ ).checkbox;
// Inpaint Full Resolution Checkbox
state.ctxmenu.fullResolutionLabel = _toolbar_input.checkbox(
state,
"fullResolution",
- "Inpaint Full Resolution"
- ).label;
+ "Inpaint Full Resolution",
+ "icon-expand"
+ ).checkbox;
// Denoising Strength Slider
state.ctxmenu.denoisingStrengthSlider = _toolbar_input.slider(
@@ -2076,8 +2089,9 @@ const img2imgTool = () =>
state.ctxmenu.borderMaskGradientCheckbox = _toolbar_input.checkbox(
state,
"gradient",
- "Border Mask Gradient"
- ).label;
+ "Border Mask Gradient",
+ "icon-box-select"
+ ).checkbox;
// Border Mask Size Slider
state.ctxmenu.borderMaskSlider = _toolbar_input.slider(
@@ -2124,20 +2138,22 @@ const img2imgTool = () =>
}
menu.appendChild(state.ctxmenu.cursorSizeSlider);
- menu.appendChild(state.ctxmenu.snapToGridLabel);
- menu.appendChild(document.createElement("br"));
- menu.appendChild(state.ctxmenu.invertMaskLabel);
- menu.appendChild(document.createElement("br"));
- menu.appendChild(state.ctxmenu.keepUnmaskedLabel);
+ const array = document.createElement("div");
+ array.classList.add("checkbox-array");
+ array.appendChild(state.ctxmenu.snapToGridLabel);
+ array.appendChild(state.ctxmenu.invertMaskLabel);
+ array.appendChild(state.ctxmenu.preserveMasksLabel);
+ array.appendChild(state.ctxmenu.keepUnmaskedLabel);
+ menu.appendChild(array);
menu.appendChild(state.ctxmenu.keepUnmaskedBlurSlider);
menu.appendChild(state.ctxmenu.keepUnmaskedBlurSliderLinebreak);
- menu.appendChild(state.ctxmenu.preserveMasksLabel);
- 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);
+ const btnArray2 = document.createElement("div");
+ btnArray2.classList.add("checkbox-array");
+ btnArray2.appendChild(state.ctxmenu.fullResolutionLabel);
+ btnArray2.appendChild(state.ctxmenu.borderMaskGradientCheckbox);
+ menu.appendChild(btnArray2);
menu.appendChild(state.ctxmenu.borderMaskSlider);
menu.appendChild(state.ctxmenu.eagerGenerateCountLabel);
},
diff --git a/js/ui/tool/interrogate.js b/js/ui/tool/interrogate.js
index 2090c03..b7fc403 100644
--- a/js/ui/tool/interrogate.js
+++ b/js/ui/tool/interrogate.js
@@ -97,8 +97,9 @@ const interrogateTool = () =>
state.ctxmenu.snapToGridLabel = _toolbar_input.checkbox(
state,
"snapToGrid",
- "Snap To Grid"
- ).label;
+ "Snap To Grid",
+ "icon-grid"
+ ).checkbox;
}
menu.appendChild(state.ctxmenu.cursorSizeSlider);
diff --git a/js/ui/tool/select.js b/js/ui/tool/select.js
index c62cf14..11eb700 100644
--- a/js/ui/tool/select.js
+++ b/js/ui/tool/select.js
@@ -633,23 +633,26 @@ const selectTransformTool = () =>
state.ctxmenu.snapToGridLabel = _toolbar_input.checkbox(
state,
"snapToGrid",
- "Snap To Grid"
- ).label;
+ "Snap To Grid",
+ "icon-grid"
+ ).checkbox;
// Keep Aspect Ratio
state.ctxmenu.keepAspectRatioLabel = _toolbar_input.checkbox(
state,
"keepAspectRatio",
- "Keep Aspect Ratio"
- ).label;
+ "Keep Aspect Ratio",
+ "icon-maximize"
+ ).checkbox;
// Use Clipboard
const clipboardCheckbox = _toolbar_input.checkbox(
state,
"useClipboard",
- "Use clipboard"
+ "Use clipboard",
+ "icon-clipboard-list"
);
- state.ctxmenu.useClipboardLabel = clipboardCheckbox.label;
+ state.ctxmenu.useClipboardLabel = clipboardCheckbox.checkbox;
if (!(navigator.clipboard && navigator.clipboard.write))
clipboardCheckbox.checkbox.disabled = true; // Disable if not available
@@ -760,11 +763,12 @@ const selectTransformTool = () =>
state.ctxmenu.actionArray = actionArray;
state.ctxmenu.visibleActionArray = visibleActionArray;
}
- menu.appendChild(state.ctxmenu.snapToGridLabel);
- menu.appendChild(document.createElement("br"));
- menu.appendChild(state.ctxmenu.keepAspectRatioLabel);
- menu.appendChild(document.createElement("br"));
- menu.appendChild(state.ctxmenu.useClipboardLabel);
+ const array = document.createElement("div");
+ array.classList.add("checkbox-array");
+ array.appendChild(state.ctxmenu.snapToGridLabel);
+ array.appendChild(state.ctxmenu.keepAspectRatioLabel);
+ array.appendChild(state.ctxmenu.useClipboardLabel);
+ menu.appendChild(array);
menu.appendChild(state.ctxmenu.selectionPeekOpacitySlider);
menu.appendChild(state.ctxmenu.actionArray);
menu.appendChild(state.ctxmenu.visibleActionArray);
diff --git a/js/ui/tool/stamp.js b/js/ui/tool/stamp.js
index 74be4ad..fc1cb89 100644
--- a/js/ui/tool/stamp.js
+++ b/js/ui/tool/stamp.js
@@ -368,11 +368,17 @@ const stampTool = () =>
if (!state.ctxmenu) {
state.ctxmenu = {};
// Snap To Grid Checkbox
- state.ctxmenu.snapToGridLabel = _toolbar_input.checkbox(
- state,
- "snapToGrid",
- "Snap To Grid"
- ).label;
+ const array = document.createElement("div");
+ array.classList.add("checkbox-array");
+ array.appendChild(
+ _toolbar_input.checkbox(
+ state,
+ "snapToGrid",
+ "Snap To Grid",
+ "icon-grid"
+ ).checkbox
+ );
+ state.ctxmenu.snapToGridLabel = array;
// Create resource list
const uploadButtonId = `upload-btn-${guid()}`;
diff --git a/pages/configuration.html b/pages/configuration.html
index 81409ec..5e53994 100644
--- a/pages/configuration.html
+++ b/pages/configuration.html
@@ -5,12 +5,12 @@
openOutpaint 🐠
-
+
-
+
diff --git a/res/icons/clipboard-list.svg b/res/icons/clipboard-list.svg
new file mode 100644
index 0000000..45bbcf5
--- /dev/null
+++ b/res/icons/clipboard-list.svg
@@ -0,0 +1,9 @@
+
\ No newline at end of file
diff --git a/res/icons/equal.svg b/res/icons/equal.svg
new file mode 100644
index 0000000..6b5c5c8
--- /dev/null
+++ b/res/icons/equal.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/res/icons/expand.svg b/res/icons/expand.svg
new file mode 100644
index 0000000..6f5864a
--- /dev/null
+++ b/res/icons/expand.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/res/icons/grid.svg b/res/icons/grid.svg
new file mode 100644
index 0000000..a7daf6d
--- /dev/null
+++ b/res/icons/grid.svg
@@ -0,0 +1,8 @@
+
\ No newline at end of file
diff --git a/res/icons/maximize.svg b/res/icons/maximize.svg
new file mode 100644
index 0000000..5c8a6e3
--- /dev/null
+++ b/res/icons/maximize.svg
@@ -0,0 +1,7 @@
+
\ No newline at end of file
diff --git a/res/icons/pin.svg b/res/icons/pin.svg
new file mode 100644
index 0000000..aa858be
--- /dev/null
+++ b/res/icons/pin.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/res/icons/square.svg b/res/icons/square.svg
new file mode 100644
index 0000000..ba8d095
--- /dev/null
+++ b/res/icons/square.svg
@@ -0,0 +1,4 @@
+
\ No newline at end of file
diff --git a/res/icons/venetian-mask.svg b/res/icons/venetian-mask.svg
new file mode 100644
index 0000000..6cd8962
--- /dev/null
+++ b/res/icons/venetian-mask.svg
@@ -0,0 +1,6 @@
+
\ No newline at end of file