diff --git a/README.md b/README.md index ebfb730..da9d13e 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ this is a completely vanilla javascript and html canvas outpainting convenience - 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`] 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_** +- extremely limited, janky support for a shockingly restrictive list of A1111 extensions including controlnet inpainting for legitimately [magic](https://github.com/Mikubill/sd-webui-controlnet/discussions/1464) [promptless inpainting](https://github.com/Mikubill/sd-webui-controlnet/discussions/1143) and [outpainting](https://github.com/Mikubill/sd-webui-controlnet/discussions/1597) - a very nicely functional and familiar layer system - save, load, import, and export workspaces - includes all your layers, history, canvas size, you name it! - inpainting/touchup mask brush diff --git a/css/icons.css b/css/icons.css index fefeb77..08e7c00 100644 --- a/css/icons.css +++ b/css/icons.css @@ -128,6 +128,12 @@ mask-image: url("../res/icons/slice.svg"); } +.ui.inline-icon.icon-joystick::after, +.ui.icon > .icon-joystick { + -webkit-mask-image: url("../res/icons/joystick.svg"); + mask-image: url("../res/icons/joystick.svg"); +} + .ui.inline-icon.icon-save::after, .ui.icon > .icon-save { -webkit-mask-image: url("../res/icons/save.svg"); diff --git a/defaultscripts.json b/defaultscripts.json index 885e16e..3fa06e7 100644 --- a/defaultscripts.json +++ b/defaultscripts.json @@ -9,8 +9,8 @@ "scriptValues": "[false, false, \"positive\", \"comma\", 2]" }, "X/Y/Z plot": { - "titleText": "Params:\nx_type (int): index of axis type (see below) //def: 3\nx_values (mixed, str) //def: \"0.00-0.99 [4]\"\ny_type (int) //def: 4\ny_values (mixed, str) //def: \"5-30 [4]\"\nz_type (int) //def: 6\nz_values (mixed, str) //def: \"2.5-12.5 [4]\"\ndraw_legend (bool): return grid of all images //def: false\ninclude_lone_images (bool): return individual images //def: true\ninclude_subgrids (bool) //def: false\nno_fixed_seeds (bool): use different seeds for each picture //def: false\nmargin_size (int): grid margins in px //def: 2\n\nAvailable axis types:\n0: Nothing\n1: Seed\n2: Var. seed\n3: Var. strength\n4: Steps\n5: Hires steps (txt2img only)\n6: CFG Scale\n7: Image CFG Scale (img2img with instructPix2Pix only)\n8: Prompt S/R\n9: Prompt order\n10: Sampler (txt2img only)\n11: Sampler (img2img only)\n12: Checkpoint Name\n13: Sigma Churn\n14: Sigma min\n15: Sigma max\n16: Sigma noise\n17: Eta\n18: Clip skip\n19: Denoising\n20: Hires upscaler (txt2img only)\n21: Cond. Image Mask Weight (img2img only)\n22: VAE\n23: Styles\n24: UniPC Order\n25: Face Restore", - "scriptValues": "[3, \"0.00-0.99 [4]\", 4, \"5-30 [4]\", 6, \"2.5-12.5 [4]\", false, true, false, false, 2]" + "titleText": "Params:\nx_type (int): index of axis type (see below) //def: 3\nx_values (mixed, str) //def: \"0.00-0.99 [4]\"\nx_values_dropdown (NOIDEA) //def: \"\"\ny_type (int) //def: 4\ny_values (mixed, str) //def: \"5-30 [4]\"\ny_values_dropdown (NOIDEA) //def: \"\"\nz_type (int) //def: 6\nz_values (mixed, str) //def: \"2.5-12.5 [4]\"\nz_values_dropdown (NOIDEA) //def: \"\"\ndraw_legend (bool): return grid of all images //def: false\ninclude_lone_images (bool): return individual images //def: true\ninclude_subgrids (bool) //def: false\nno_fixed_seeds (bool): use different seeds for each picture //def: false\nmargin_size (int): grid margins in px //def: 2\n\nAvailable axis types:\n0: Nothing\n1: Seed\n2: Var. seed\n3: Var. strength\n4: Steps\n5: Hires steps (txt2img only)\n6: CFG Scale\n7: Image CFG Scale (img2img with instructPix2Pix only)\n8: Prompt S/R\n9: Prompt order\n10: Sampler (txt2img only)\n11: Sampler (img2img only)\n12: Checkpoint Name\n13: Negative Guidance minimum sigma\n14: Sigma Churn\n15: Sigma min\n16: Sigma max\n17: Sigma noise\n18: Schedule Type\n19: Schedule min sigma\n20:Schedule max sigma\n21: Schedule rho\n22: Eta\n23: Clip skip\n24: Denoising\n25: Hires upscaler (txt2img only)\n26: Cond. Image Mask Weight (img2img only)\n27: VAE\n23: Styles\n28: UniPC Order\n29: Face Restore\n30: Token merging ratio\n31: Token merging ratio hi-res", + "scriptValues": "[3, \"0.00-0.99 [4]\", \"\", 4, \"5-30 [4]\", \"\", 6, \"2.5-12.5 [4]\", \"\", false, true, false, false, 2]" } } } diff --git a/index.html b/index.html index 12924b1..0bdf911 100644 --- a/index.html +++ b/index.html @@ -5,7 +5,7 @@ openOutpaint 🐠 - + @@ -143,10 +143,12 @@ id="seed" onchange="changeSeed()" min="-1" - max="9999999999" + max="99999999999999999999" value="-1" step="1" />
+ +

@@ -195,7 +197,48 @@ step="1" onchange="changeMaskBlur()" /> - + + +
+ + +
+ + +
+ +
+ +
+ + +
+ + + +
@@ -244,7 +287,8 @@
- Alpha release v0.0.15.3 (20230410.001) + Alpha release v0.0.16 + v20230708.001
+ @@ -466,7 +511,7 @@ - + - + diff --git a/js/extensions.js b/js/extensions.js new file mode 100644 index 0000000..e0a6b5b --- /dev/null +++ b/js/extensions.js @@ -0,0 +1,160 @@ +/** + * Extensions helper thing or class or whatever + */ + +const extensions = { + // alwaysOnScriptsData: {}, + alwaysOnScripts: false, + controlNetEnabled: false, + controlNetActive: false, + selectedControlNetModel: null, + selectedControlNetModule: null, + dynamicPromptsEnabled: false, + dynamicPromptsActive: false, + dynamicPromptsAlwaysonScriptName: null, //GRUMBLE GRUMBLE + enabledExtensions: [], + controlNetModels: null, + controlNetModules: null, + + async getExtensions( + controlNetModelAutoComplete, + controlNetModuleAutoComplete + ) { + const allowedExtensions = [ + "controlnet", + // "none", + // "adetailer", // no API, can't verify available models + "dynamic prompts", //seriously >:( why put version in the name, now i have to fuzzy match it - just simply enabled or not? no API but so so good + //"segment anything", // ... API lets me get model but not processor?!?!?! + //"self attention guidance", // no API but useful, just enabled button, scale and threshold sliders? + ]; + // check http://127.0.0.1:7860/sdapi/v1/scripts for extensions + // if any of the allowed extensions are found, add them to the list + var url = document.getElementById("host").value + "/sdapi/v1/scripts"; + try { + const response = await fetch(url); + const data = await response.json(); + // enable checkboxes for extensions based on existence in data + data.img2img + .filter((extension) => { + return allowedExtensions.some((allowedExtension) => { + return extension.toLowerCase().includes(allowedExtension); + }); + }) + .forEach((extension) => { + this.enabledExtensions.push(extension); + }); + } catch (e) { + console.warn("[index] Failed to fetch extensions"); + console.warn(e); + } + this.checkForDynamicPrompts(); + this.checkForControlNet( + controlNetModelAutoComplete, + controlNetModuleAutoComplete + ); + //checkForSAM(); //or inpaintAnything or something i dunno + //checkForADetailer(); //? this one seems iffy + //checkForSAG(); //?? + }, + + async checkForDynamicPrompts() { + if ( + this.enabledExtensions.filter((e) => e.includes("dynamic prompts")) + .length > 0 + ) { + // Dynamic Prompts found, enable checkbox + this.alwaysOnScripts = true; + this.dynamicPromptsAlwaysonScriptName = + this.enabledExtensions[ + this.enabledExtensions.findIndex((e) => e.includes("dynamic prompts")) + ]; + // this.alwaysOnScriptsData[this.dynamicPromptsAlwaysonScriptName] = {}; + this.dynamicPromptsEnabled = true; + document.getElementById("cbxDynPrompts").disabled = false; + } + // basically param 0 is true for on, false for off, that's it + }, + + async checkForControlNet( + controlNetModelAutoComplete, + controlNetModuleAutoComplete + ) { + var url = document.getElementById("host").value + "/controlnet/version"; + + try { + const response = await fetch(url); + const data = await response.json(); + + if ( + data.version > 0 && + this.enabledExtensions.filter((e) => e.includes("controlnet")).length > + 0 + ) { + // ControlNet found + this.alwaysOnScripts = true; + this.controlNetEnabled = true; + document.getElementById("cbxControlNet").disabled = false; + // ok cool so now we can get the models and modules + this.getModels(controlNetModelAutoComplete); + this.getModules(controlNetModuleAutoComplete); + } + } catch (e) { + // ?? + global.controlnetAPI = false; + } + }, + async getModels(controlNetModelAutoComplete) { + // only worry about inpaint models for now + var url = document.getElementById("host").value + "/controlnet/model_list"; + + try { + const response = await fetch(url); + const data = await response.json(); + + this.controlNetModels = data.model_list; + } catch (e) { + console.warn("[extensions] Failed to fetch controlnet models"); + console.warn(e); + } + + let opt = null; + opt = this.controlNetModels + .filter((m) => m.includes("inpaint")) + .map((option) => ({ + name: option, + value: option, + })); + + controlNetModelAutoComplete.options = opt; + }, + async getModules(controlNetModuleAutoComplete) { + const allowedModules = ["reference", "inpaint"]; + var url = document.getElementById("host").value + "/controlnet/module_list"; + + try { + const response = await fetch(url); + const data = await response.json(); + + this.controlNetModules = data; + } catch (e) { + console.warn("[extensions] Failed to fetch controlnet modules"); + console.warn(e); + } + + let opt = null; + opt = this.controlNetModules.module_list + .filter((m) => m.includes("inpaint")) // why is there just "inpaint" in the modules if it's not in the ui + .map((option) => ({ + name: option, + value: option, + })); + + opt.push({ + name: "inpaint_global_harmonious", + value: "inpaint_global_harmonious", // WTF WHY IS THIS ONE NOT LISTED IN MODULES BUT DISTINCT IN THE API CALL?!?!?!??!??! it is slightly different from "inpaint" from what i can tell + }); + + controlNetModuleAutoComplete.options = opt; + }, +}; diff --git a/js/index.js b/js/index.js index 0b4523b..1f75638 100644 --- a/js/index.js +++ b/js/index.js @@ -171,6 +171,7 @@ function startup() { changeHiResSquare(); changeRestoreFaces(); changeSyncCursorSize(); + changeControlNetExtension(); checkFocus(); refreshScripts(); } @@ -421,6 +422,13 @@ async function testHostConnection() { getSamplers(); getUpscalers(); getModels(); + extensions.getExtensions( + controlNetModelAutoComplete, + controlNetModuleAutoComplete + ); + getLoras(); + // getTIEmbeddings(); + // getHypernets(); firstTimeOnline = false; } break; @@ -644,6 +652,18 @@ modelAutoComplete.onchange.on(({value}) => { ).style.backgroundColor = "#fcc"; }); +let loraAutoComplete = createAutoComplete( + "LoRa", + document.getElementById("lora-ac-select") +); +loraAutoComplete.onchange.on(({value}) => { + // add selected lora to the end of the prompt + let passVal = " "; + let promptInput = document.getElementById("prompt"); + promptInput.value += passVal; + let promptThing = prompt; +}); + const samplerAutoComplete = createAutoComplete( "Sampler", document.getElementById("sampler-ac-select") @@ -659,6 +679,29 @@ const hrFixUpscalerAutoComplete = createAutoComplete( document.getElementById("hrFixUpscaler") ); +let controlNetModelAutoComplete = createAutoComplete( + "ControlNet Model", + document.getElementById("controlNetModel-ac-select") +); + +controlNetModelAutoComplete.onchange.on(({value}) => { + extensions.selectedControlNetModel = value; +}); + +let controlNetModuleAutoComplete = createAutoComplete( + "ControlNet Module", + document.getElementById("controlNetModule-ac-select") +); + +controlNetModuleAutoComplete.onchange.on(({value}) => { + extensions.selectedControlNetModule = value; +}); + +// const extensionsAutoComplete = createAutoComplete( +// "Extension", +// document.getElementById("extension-ac-select") +// ); + hrFixUpscalerAutoComplete.onchange.on(({value}) => { stableDiffusionData.hr_upscaler = value; localStorage.setItem(`openoutpaint/hr_upscaler`, value); @@ -798,6 +841,25 @@ function changeHRFY() { document.getElementById("hr_resize_y").value; } +function changeDynamicPromptsExtension() { + extensions.dynamicPromptsActive = + document.getElementById("cbxDynPrompts").checked; +} + +function changeControlNetExtension() { + extensions.controlNetActive = + document.getElementById("cbxControlNet").checked; + if (extensions.controlNetActive) { + document + .querySelectorAll(".controlnetElement") + .forEach((el) => el.classList.remove("invisible")); + } else { + document + .querySelectorAll(".controlnetElement") + .forEach((el) => el.classList.add("invisible")); + } +} + function changeHiResFix() { stableDiffusionData.enable_hr = Boolean( document.getElementById("cbxHRFix").checked @@ -1130,6 +1192,24 @@ async function getModels(refresh = false) { } } +async function getLoras() { + var url = document.getElementById("host").value + "/sdapi/v1/loras"; + let opt = null; + + try { + const response = await fetch(url); + const data = await response.json(); + + loraAutoComplete.options = data.map((lora) => ({ + name: lora.name, + value: lora.name, + })); + } catch (e) { + console.warn("[index] Failed to fetch loras"); + console.warn(e); + } +} + async function getConfig() { var url = document.getElementById("host").value + "/sdapi/v1/options"; @@ -1247,6 +1327,7 @@ async function getSamplers() { console.warn(e); } } + async function upscaleAndDownload() { // Future improvements: some upscalers take a while to upscale, so we should show a loading bar or something, also a slider for the upscale amount diff --git a/js/ui/tool/dream.js b/js/ui/tool/dream.js index 5798598..c9ce8fd 100644 --- a/js/ui/tool/dream.js +++ b/js/ui/tool/dream.js @@ -1001,6 +1001,7 @@ const _generate = async (endpoint, request, bb, options = {}) => { const dream_generate_callback = async (bb, resolution, state) => { // Build request to the API const request = {}; + const canvasTransport = {}; //this is the worst idea but i hate myself so i'm doing it anyway Object.assign(request, stableDiffusionData); request.width = resolution.w; @@ -1013,8 +1014,125 @@ const dream_generate_callback = async (bb, resolution, state) => { // Get visible pixels const visibleCanvas = uil.getVisible(bb); - // Use txt2img if canvas is blank - if (isCanvasBlank(0, 0, bb.w, bb.h, visibleCanvas)) { + if (extensions.alwaysOnScripts) { + buildAlwaysOnScripts(state); + } + + // Use txt2img if canvas is blank or if controlnet is active because "Allow inpaint in txt2img. This is necessary because txt2img has high-res fix" as per https://github.com/Mikubill/sd-webui-controlnet/discussions/1464 + if ( + isCanvasBlank(0, 0, bb.w, bb.h, visibleCanvas) || + (extensions.controlNetActive && toolbar._current_tool.name === "Dream") + ) { + //TODO why doesn't smooth rendering toggle persist/localstorage? why am i putting this here? because i'm lazy + + //TODO this logic seems crappy, fix it + if (!isCanvasBlank(0, 0, bb.w, bb.h, visibleCanvas)) { + // get input image + // Temporary canvas for init image and mask generation + const bbCanvas = document.createElement("canvas"); + bbCanvas.width = bb.w; + bbCanvas.height = bb.h; + const bbCtx = bbCanvas.getContext("2d"); + + const maskCanvas = document.createElement("canvas"); + maskCanvas.width = request.width; + maskCanvas.height = request.height; + const maskCtx = maskCanvas.getContext("2d"); + + const initCanvas = document.createElement("canvas"); + initCanvas.width = request.width; + initCanvas.height = request.height; + const initCtx = initCanvas.getContext("2d"); + + bbCtx.fillStyle = "#000F"; + + // Get init image + initCtx.fillRect(0, 0, request.width, request.height); + initCtx.drawImage( + visibleCanvas, + 0, + 0, + bb.w, + bb.h, + 0, + 0, + request.width, + request.height + ); + // request.init_images = [initCanvas.toDataURL()]; + + // Get mask image + bbCtx.fillStyle = "#000F"; + bbCtx.fillRect(0, 0, bb.w, bb.h); + 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 + bbCtx.globalCompositeOperation = "destination-in"; + bbCtx.drawImage( + maskPaintCanvas, + bb.x, + bb.y, + bb.w, + bb.h, + 0, + 0, + bb.w, + bb.h + ); + + bbCtx.globalCompositeOperation = "destination-in"; + bbCtx.drawImage(visibleCanvas, 0, 0); + } else { + bbCtx.globalCompositeOperation = "destination-in"; + bbCtx.drawImage(visibleCanvas, 0, 0); + // 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 + bbCtx.globalCompositeOperation = "destination-atop"; + bbCtx.fillStyle = "#FFFF"; + bbCtx.fillRect(0, 0, bb.w, bb.h); + applyOvermask(bbCanvas, bbCtx, state.overMaskPx); + } + + bbCtx.globalCompositeOperation = "destination-out"; // ??? + bbCtx.drawImage( + maskPaintCanvas, + bb.x, + bb.y, + bb.w, + bb.h, + 0, + 0, + bb.w, + bb.h + ); + } + + bbCtx.globalCompositeOperation = "destination-atop"; + bbCtx.fillStyle = "#FFFF"; + bbCtx.fillRect(0, 0, bb.w, bb.h); + + maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height); + maskCtx.drawImage( + bbCanvas, + 0, + 0, + bb.w, + bb.h, + 0, + 0, + request.width, + request.height + ); + canvasTransport.initCanvas = initCanvas; + canvasTransport.maskCanvas = maskCanvas; + + // getImageAndMask(visibleCanvas, bb, request, state); //FFFFF + } + + // request.alwayson_scripts = state.alwayson_scripts; + if (!global.isOldHRFix && request.enable_hr) { /** * try and make the new HRfix method useful for our purposes @@ -1083,6 +1201,31 @@ const dream_generate_callback = async (bb, resolution, state) => { ? stableDiffusionData.hr_denoising_strength : 1; + // add dynamic prompts stuff if it exists because it needs to be explicitly disabled if we don't want it + if (extensions.dynamicPromptsEnabled) { + addDynamicPromptsToAlwaysOnScripts(state); + } + if ( + extensions.controlNetActive && + !isCanvasBlank(0, 0, bb.w, bb.h, visibleCanvas) + ) { + addControlNetToAlwaysOnScripts( + state, + canvasTransport.initCanvas, + canvasTransport.maskCanvas + ); + } else { + console.warn( + "[dream] controlnet inpaint/outpaint enabled for null image, defaulting to normal txt2img dream" + ); + } + + if (extensions.alwaysOnScripts) { + // check again just to be sure because i'm an idiot? + // addControlNetToAlwaysOnScripts(state); + // addDynamicPromptsToAlwaysOnScripts(state); + request.alwayson_scripts = state.alwayson_scripts; + } // Dream _generate("txt2img", request, bb); } else { @@ -1185,10 +1328,25 @@ const dream_generate_callback = async (bb, resolution, state) => { request.width, request.height ); + // getImageAndMask(visibleCanvas, bb, request, state); // why is not working ffff request.mask = maskCanvas.toDataURL(); request.inpainting_fill = stableDiffusionData.outpainting_fill; request.image_cfg_scale = stableDiffusionData.image_cfg_scale; + // add dynamic prompts stuff if it's enabled + if (extensions.dynamicPromptsEnabled) { + addDynamicPromptsToAlwaysOnScripts(state); + } + if (extensions.controlNetActive) { + addControlNetToAlwaysOnScripts(state, initCanvas, maskCanvas); + } + if (extensions.alwaysOnScripts) { + // check again just to be sure because i'm an idiot? + // addControlNetToAlwaysOnScripts(state); + // addDynamicPromptsToAlwaysOnScripts(state); + request.alwayson_scripts = state.alwayson_scripts; + } + // Dream _generate("img2img", request, bb, { keepUnmask: state.keepUnmasked ? bbCanvas : null, @@ -1256,6 +1414,10 @@ const dream_img2img_callback = (bb, resolution, state) => { // Get visible pixels const visibleCanvas = uil.getVisible(bb); + if (extensions.alwaysOnScripts) { + buildAlwaysOnScripts(state); + } + // Do nothing if no image exists if (isCanvasBlank(0, 0, bb.w, bb.h, visibleCanvas)) return; @@ -1400,6 +1562,20 @@ const dream_img2img_callback = (bb, resolution, state) => { request.mask = reqCanvas.toDataURL(); request.inpaint_full_res = state.fullResolution; + // add dynamic prompts stuff if it's enabled + if (extensions.dynamicPromptsEnabled) { + addDynamicPromptsToAlwaysOnScripts(state); + } + if (extensions.controlNetActive) { + addControlNetToAlwaysOnScripts(state, null, null); + } + if (extensions.alwaysOnScripts) { + // check again just to be sure because i'm an idiot? + // addControlNetToAlwaysOnScripts(state); + // addDynamicPromptsToAlwaysOnScripts(state); + request.alwayson_scripts = state.alwayson_scripts; + } + // Dream _generate("img2img", request, bb, { keepUnmask: state.keepUnmasked ? bbCanvas : null, @@ -1550,6 +1726,15 @@ const dreamTool = () => ...mouse.coords.world.pos, }; + // state.alwayson_scripts = {}; + // state.alwayson_scripts.controlnet = {}; + // state.alwayson_scripts.controlnet.args = [ + // { + // module: "none", + // model: "None", //docs have this casing, is that necessary? + // }, + // ]; + /** * Selection handlers */ @@ -1879,6 +2064,15 @@ const dreamTool = () => } ).checkbox; + // controlnet checkbox + state.ctxmenu.controlNetLabel = _toolbar_input.checkbox( + state, + "openoutpaint/dream-controlnet", + "controlNet", + "Toggle ControlNet In/Outpainting", + "icon-joystick" + ).checkbox; + // Overmasking Slider state.ctxmenu.overMaskPxLabel = _toolbar_input.slider( state, @@ -1946,6 +2140,10 @@ const dreamTool = () => //menu.appendChild(document.createElement("br")); array.appendChild(state.ctxmenu.keepUnmaskedLabel); array.appendChild(state.ctxmenu.removeBackgroundLabel); + //TODO: if (global.controlnetAPI) { //but figure out how to update the UI after doing so + // never mind i think i'm using an extension menu instead + // array.appendChild(state.ctxmenu.controlNetLabel); + //} menu.appendChild(array); menu.appendChild(state.ctxmenu.keepUnmaskedBlurSlider); menu.appendChild(state.ctxmenu.carveBlurSlider); @@ -2628,3 +2826,136 @@ const img2imgTool = () => const sendSeed = (seed) => { stableDiffusionData.seed = document.getElementById("seed").value = seed; }; + +function buildAlwaysOnScripts(state) { + if (extensions.alwaysOnScripts) { + state.alwayson_scripts = {}; + } +} + +function addDynamicPromptsToAlwaysOnScripts(state) { + if (extensions.dynamicPromptsEnabled) { + state.alwayson_scripts[extensions.dynamicPromptsAlwaysonScriptName] = {}; + state.alwayson_scripts[extensions.dynamicPromptsAlwaysonScriptName].args = [ + extensions.dynamicPromptsActive, + ]; + } +} + +function addControlNetToAlwaysOnScripts(state, initCanvas, maskCanvas) { + if (extensions.controlNetEnabled && extensions.controlNetActive) { + state.alwayson_scripts.controlnet = {}; + if (initCanvas == null && maskCanvas == null) { + //img2img? + state.alwayson_scripts.controlnet.args = [ + { + module: extensions.selectedControlNetModule, + model: extensions.selectedControlNetModel, + control_mode: document.getElementById("controlNetMode-select").value, + processor_res: 64, + resize_mode: document.getElementById("controlNetResize-select").value, + // resize mode? + // weights / steps? + }, + ]; + } else { + state.alwayson_scripts.controlnet.args = [ + { + module: extensions.selectedControlNetModule, + model: extensions.selectedControlNetModel, + control_mode: document.getElementById("controlNetMode-select").value, + input_image: initCanvas.toDataURL(), + mask: maskCanvas.toDataURL(), + processor_res: 64, + resize_mode: document.getElementById("controlNetResize-select").value, + // resize mode? + // weights / steps? + }, + ]; + } + } + + // request.alwayson_scripts = state.alwayson_scripts; + // } +} + +// function getImageAndMask(visibleCanvas, bb, request, state) { +// // get input image +// // Temporary canvas for init image and mask generation +// const bbCanvas = document.createElement("canvas"); +// bbCanvas.width = bb.w; +// bbCanvas.height = bb.h; +// const bbCtx = bbCanvas.getContext("2d"); + +// const maskCanvas = document.createElement("canvas"); +// maskCanvas.width = request.width; +// maskCanvas.height = request.height; +// const maskCtx = maskCanvas.getContext("2d"); + +// const initCanvas = document.createElement("canvas"); +// initCanvas.width = request.width; +// initCanvas.height = request.height; +// const initCtx = initCanvas.getContext("2d"); + +// bbCtx.fillStyle = "#000F"; + +// // Get init image +// initCtx.fillRect(0, 0, request.width, request.height); +// initCtx.drawImage( +// visibleCanvas, +// 0, +// 0, +// bb.w, +// bb.h, +// 0, +// 0, +// request.width, +// request.height +// ); +// // request.init_images = [initCanvas.toDataURL()]; + +// // Get mask image +// bbCtx.fillStyle = "#000F"; +// bbCtx.fillRect(0, 0, bb.w, bb.h); +// 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 +// bbCtx.globalCompositeOperation = "destination-in"; +// bbCtx.drawImage(maskPaintCanvas, bb.x, bb.y, bb.w, bb.h, 0, 0, bb.w, bb.h); + +// bbCtx.globalCompositeOperation = "destination-in"; +// bbCtx.drawImage(visibleCanvas, 0, 0); +// } else { +// bbCtx.globalCompositeOperation = "destination-in"; +// bbCtx.drawImage(visibleCanvas, 0, 0); +// // 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 +// bbCtx.globalCompositeOperation = "destination-atop"; +// bbCtx.fillStyle = "#FFFF"; +// bbCtx.fillRect(0, 0, bb.w, bb.h); +// applyOvermask(bbCanvas, bbCtx, state.overMaskPx); +// } + +// bbCtx.globalCompositeOperation = "destination-out"; // ??? +// bbCtx.drawImage(maskPaintCanvas, bb.x, bb.y, bb.w, bb.h, 0, 0, bb.w, bb.h); +// } + +// bbCtx.globalCompositeOperation = "destination-atop"; +// bbCtx.fillStyle = "#FFFF"; +// bbCtx.fillRect(0, 0, bb.w, bb.h); + +// maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height); +// maskCtx.drawImage( +// bbCanvas, +// 0, +// 0, +// bb.w, +// bb.h, +// 0, +// 0, +// request.width, +// request.height +// ); +// } diff --git a/pages/configuration.html b/pages/configuration.html index b14b873..747debd 100644 --- a/pages/configuration.html +++ b/pages/configuration.html @@ -5,7 +5,7 @@ openOutpaint 🐠 - + @@ -217,6 +217,7 @@ localStorage.getItem("openoutpaint/settings.smooth") === null ? true : localStorage.getItem("openoutpaint/settings.smooth") === "true"; + smoothRendering.checked = _enable_smooth; writeToLocalStorage(); diff --git a/res/icons/joystick.svg b/res/icons/joystick.svg new file mode 100644 index 0000000..1b5e569 --- /dev/null +++ b/res/icons/joystick.svg @@ -0,0 +1 @@ + \ No newline at end of file