From e5d1b2326819d87009429eb2c115015e4dc756e9 Mon Sep 17 00:00:00 2001
From: tim h <zero01101@gmail.com>
Date: Sat, 8 Jul 2023 15:21:58 -0500
Subject: [PATCH] sort of working? time to test against webui

---
 js/extensions.js    |   4 +-
 js/index.js         |  12 +++-
 js/ui/tool/dream.js | 157 ++++++++++++++++++++++++++++++++++++--------
 3 files changed, 141 insertions(+), 32 deletions(-)

diff --git a/js/extensions.js b/js/extensions.js
index b698869..d8b9ace 100644
--- a/js/extensions.js
+++ b/js/extensions.js
@@ -7,6 +7,8 @@ const extensions = {
 	alwaysOnScripts: false,
 	controlNetEnabled: false,
 	controlNetActive: false,
+	selectedControlNetModel: null,
+	selectedControlNetModule: null,
 	dynamicPromptsEnabled: false,
 	dynamicPromptsActive: false,
 	dynamicPromptsAlwaysonScriptName: null, //GRUMBLE GRUMBLE
@@ -142,7 +144,7 @@ const extensions = {
 
 		let opt = null;
 		opt = this.controlNetModules.module_list
-			.filter((m) => m.includes("inpaint_")) // why is there just "inpaint" in the modules
+			.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,
diff --git a/js/index.js b/js/index.js
index d0a722c..6de13b1 100644
--- a/js/index.js
+++ b/js/index.js
@@ -667,16 +667,24 @@ const hrFixUpscalerAutoComplete = createAutoComplete(
 	document.getElementById("hrFixUpscaler")
 );
 
-const controlNetModelAutoComplete = createAutoComplete(
+let controlNetModelAutoComplete = createAutoComplete(
 	"ControlNet Model",
 	document.getElementById("controlNetModel-ac-select")
 );
 
-const controlNetModuleAutoComplete = createAutoComplete(
+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")
diff --git a/js/ui/tool/dream.js b/js/ui/tool/dream.js
index be872f9..62dd5bb 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;
@@ -1017,17 +1018,16 @@ const dream_generate_callback = async (bb, resolution, state) => {
 		buildAlwaysOnScripts(state);
 	}
 
-	// Use txt2img if canvas is blank
+	// 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) ||
-		(state.controlNet && global.controlnetAPI)
+		(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
-			//TODO make this DRY from below img2img code
 			// Temporary canvas for init image and mask generation
 			const bbCanvas = document.createElement("canvas");
 			bbCanvas.width = bb.w;
@@ -1059,6 +1059,7 @@ const dream_generate_callback = async (bb, resolution, state) => {
 				request.width,
 				request.height
 			);
+			// request.init_images = [initCanvas.toDataURL()];
 
 			// Get mask image
 			bbCtx.fillStyle = "#000F";
@@ -1124,6 +1125,10 @@ const dream_generate_callback = async (bb, resolution, state) => {
 				request.width,
 				request.height
 			);
+			canvasTransport.initCanvas = initCanvas;
+			canvasTransport.maskCanvas = maskCanvas;
+
+			// getImageAndMask(visibleCanvas, bb, request, state); //FFFFF
 		}
 
 		// request.alwayson_scripts = state.alwayson_scripts;
@@ -1196,15 +1201,29 @@ const dream_generate_callback = async (bb, resolution, state) => {
 				? stableDiffusionData.hr_denoising_strength
 				: 1;
 
-		// add dynamic prompts stuff if it's enabled
-		if (extensions.dynamicPromptsActive) {
+		// 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) {
-			addControlNetToAlwaysOnScripts(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
@@ -1309,19 +1328,22 @@ 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.dynamicPromptsActive) {
+		if (extensions.dynamicPromptsEnabled) {
 			addDynamicPromptsToAlwaysOnScripts(state);
 		}
 		if (extensions.controlNetActive) {
-			addControlNetToAlwaysOnScripts(state);
+			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;
 		}
 
@@ -2790,15 +2812,10 @@ const sendSeed = (seed) => {
 function buildAlwaysOnScripts(state) {
 	if (extensions.alwaysOnScripts) {
 		state.alwayson_scripts = {};
-		addControlNetToAlwaysOnScripts(state);
-		addDynamicPromptsToAlwaysOnScripts(state);
 	}
-
-	//TODO find way to remove alwayson_scripts if not active?
 }
 
 function addDynamicPromptsToAlwaysOnScripts(state) {
-	//TODO ok seriously does this even NEED TO BE HERE?!?!?! it's like dynamic scripts is always on no matter what i fucking say...
 	if (extensions.dynamicPromptsEnabled) {
 		state.alwayson_scripts[extensions.dynamicPromptsAlwaysonScriptName] = {};
 		state.alwayson_scripts[extensions.dynamicPromptsAlwaysonScriptName].args = [
@@ -2807,21 +2824,103 @@ function addDynamicPromptsToAlwaysOnScripts(state) {
 	}
 }
 
-function addControlNetToAlwaysOnScripts(state) {
-	// if (state.controlNet) {
-	// 	state.alwayson_scripts = {};
-	// 	state.alwayson_scripts.controlnet = {};
-	// 	state.alwayson_scripts.controlnet.args = [
-	// 		{
-	// 			input_image: initCanvas.toDataURL(),
-	// 			mask: maskCanvas.toDataURL(),
-	// 			module: "inpaint_only+lama", //TODO make this a variable with API supplied options - inpaint_global_harmonious good for img2img for POC?
-	// 			model: "control_v11p_sd15_inpaint [ebff9138]", //TODO make this a variable with API supplied options
-	// 			// control mode?
-	// 			// resize mode?
-	// 			// weights / steps?
-	// 		},
-	// 	];
+function addControlNetToAlwaysOnScripts(state, initCanvas, maskCanvas) {
+	if (extensions.controlNetEnabled && extensions.controlNetActive) {
+		state.alwayson_scripts.controlnet = {};
+		state.alwayson_scripts.controlnet.args = [
+			{
+				input_image: initCanvas.toDataURL(),
+				mask: maskCanvas.toDataURL(),
+				module: extensions.selectedControlNetModule,
+				model: extensions.selectedControlNetModel,
+				control_mode: document.getElementById("controlNetMode-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
+// 	);
+// }