diff --git a/css/icons.css b/css/icons.css index 2cc159e..f962e4d 100644 --- a/css/icons.css +++ b/css/icons.css @@ -105,6 +105,12 @@ mask-image: url("../res/icons/paintbrush.svg"); } +.ui.inline-icon.icon-slice::after, +.ui.icon > .icon-slice { + -webkit-mask-image: url("../res/icons/slice.svg"); + mask-image: url("../res/icons/slice.svg"); +} + .ui.inline-icon.icon-save::after, .ui.icon > .icon-save { -webkit-mask-image: url("../res/icons/save.svg"); diff --git a/index.html b/index.html index aa1568c..5add2da 100644 --- a/index.html +++ b/index.html @@ -5,7 +5,7 @@ openOutpaint 🐠 - + @@ -434,7 +434,7 @@ - + @@ -468,7 +468,7 @@ src="js/ui/tool/generic.js?v=3e678e0" type="text/javascript"> - + diff --git a/js/lib/util.js b/js/lib/util.js index 2c59631..b41cf26 100644 --- a/js/lib/util.js +++ b/js/lib/util.js @@ -474,3 +474,55 @@ const makeElement = ( return el; }; + +/** + * Subtracts identical (or damn close) pixels from new dreams + * @param {HTMLCanvasElement} canvas + * @param {BoundingBox} bb + * @param {HTMLImageElement} bgImg + * @param {number}} blur + * @returns {HTMLCanvasElement} + */ + +const subtractBackground = (canvas, bb, bgImg, blur = 0) => { + // set up temp canvases + const bgCanvas = document.createElement("canvas"); + const fgCanvas = document.createElement("canvas"); + const returnCanvas = document.createElement("canvas"); + bgCanvas.width = fgCanvas.width = returnCanvas.width = bb.w; + bgCanvas.height = fgCanvas.height = returnCanvas.height = bb.h; + const bgCtx = bgCanvas.getContext("2d"); + const fgCtx = fgCanvas.getContext("2d"); + const returnCtx = returnCanvas.getContext("2d"); + returnCtx.rect(0, 0, bb.w, bb.h); + returnCtx.fill(); + // draw previous "background" image + bgCtx.drawImage(bgImg, 0, 0, bb.w, bb.h); + bgCtx.filter = "blur(" + blur + "px)"; + // ... turn that into base64 + const bgImgData = bgCtx.getImageData(0, 0, bb.w, bb.h); + // draw new image + fgCtx.drawImage(canvas, 0, 0); + const fgImgData = fgCtx.getImageData(0, 0, bb.w, bb.h); + // const blendImgData = blendCtx.getImageData(0, 0, bb.w, bb.h); + for (var i = 0; i < bgImgData.data.length; i += 4) { + var bgr = bgImgData.data[i]; + var bgg = bgImgData.data[i + 1]; + var bgb = bgImgData.data[i + 2]; + + var fgr = fgImgData.data[i]; + var fgb = fgImgData.data[i + 1]; + var fgd = fgImgData.data[i + 2]; + + const dr = Math.abs(bgr - fgr) > 10 ? fgr : 0; + const dg = Math.abs(bgg - fgb) > 10 ? fgb : 0; + const db = Math.abs(bgb - fgd) > 10 ? fgd : 0; + + const pxChanged = dr > 0 && dg > 0 && db > 0; + + fgImgData.data[i + 3] = pxChanged ? 255 : 0; + } + returnCtx.putImageData(fgImgData, 0, 0); + + return returnCanvas; +}; diff --git a/js/ui/tool/dream.js b/js/ui/tool/dream.js index a06d2e7..0ddc38f 100644 --- a/js/ui/tool/dream.js +++ b/js/ui/tool/dream.js @@ -92,9 +92,19 @@ const generating = (val) => { * * @param {"txt2img" | "img2img"} endpoint Endpoint to send the request to * @param {StableDiffusionRequest} request Stable diffusion request + * @param {BoundingBox} bb Optional: Generated image placement location * @returns {Promise} */ -const _dream = async (endpoint, request) => { +const _dream = async (endpoint, request, bb = null) => { + var bgImg = null; + if ( + endpoint == "img2img" && + bb && + toolbar._current_tool.state.removeBackground + ) { + bgImg = uil.getVisible(bb, {includeBg: false}); + } + const apiURL = `${host}${config.api.path}${endpoint}`; // if script fields are populated add them to the request var scriptName = document.getElementById("script-name-input").value; @@ -169,6 +179,7 @@ const _dream = async (endpoint, request) => { var returnData = { images: data.images, seeds: responseSubdata.all_seeds, + bgImg: bgImg, }; return returnData; }; @@ -442,7 +453,7 @@ const _generate = async (endpoint, request, bb, options = {}) => { }); imageCollection.inputElement.appendChild(interruptButton); - var dreamData = await _dream(endpoint, requestCopy); + var dreamData = await _dream(endpoint, requestCopy, bb); images.push(...dreamData.images); seeds.push(...dreamData.seeds); stopDrawingStatus = true; @@ -505,7 +516,7 @@ const _generate = async (endpoint, request, bb, options = {}) => { // load the image data after defining the closure img.src = "data:image/png;base64," + images[at]; img.addEventListener("load", () => { - const canvas = document.createElement("canvas"); + let canvas = document.createElement("canvas"); canvas.width = bb.w; canvas.height = bb.h; const ctx = canvas.getContext("2d"); @@ -519,6 +530,15 @@ const _generate = async (endpoint, request, bb, options = {}) => { commands.runCommand("addLayer", "Added Layer", {}); } + if ( + endpoint == "img2img" && + toolbar._current_tool.state.removeBackground + ) { + //TODO SERIOUSLY CHECK FOR MORE THINGS HERE + canvas = subtractBackground(canvas, bb, dreamData.bgImg, 0); + // do something else too probably idunno + } + commands.runCommand("drawImage", "Image Dream", { x: bb.x, y: bb.y, @@ -545,7 +565,7 @@ const _generate = async (endpoint, request, bb, options = {}) => { addline(` + Model = ${modelAutoComplete.value}`); addline(` + +Prompt = ${request.prompt}`); addline(` + -Prompt = ${request.negative_prompt}`); - addline(` + Styles = ${request.styles.join(", ")}`, false); + addline(` + Styles = ${request.styles.join(", ")}`, false); commands.runCommand( "drawImage", @@ -1727,6 +1747,14 @@ const dreamTool = () => "icon-paintbrush" ).checkbox; + // Remove Identical/Background Pixels Checkbox + state.ctxmenu.removeBackgroundLabel = _toolbar_input.checkbox( + state, + "removeBackground", + "Remove Identical/BG Pixels", + "icon-slice" + ).checkbox; + // Overmasking Slider state.ctxmenu.overMaskPxLabel = _toolbar_input.slider( state, @@ -1762,6 +1790,7 @@ const dreamTool = () => array.appendChild(state.ctxmenu.invertMaskLabel); array.appendChild(state.ctxmenu.preserveMasksLabel); //menu.appendChild(document.createElement("br")); + array.appendChild(state.ctxmenu.removeBackgroundLabel); array.appendChild(state.ctxmenu.keepUnmaskedLabel); menu.appendChild(array); menu.appendChild(state.ctxmenu.keepUnmaskedBlurSlider); @@ -2284,6 +2313,14 @@ const img2imgTool = () => "icon-box-select" ).checkbox; + // Remove Identical/Background Pixels Checkbox + state.ctxmenu.removeBackgroundLabel = _toolbar_input.checkbox( + state, + "removeBackground", + "Remove Identical/BG Pixels", + "icon-slice" + ).checkbox; + // Border Mask Size Slider state.ctxmenu.borderMaskSlider = _toolbar_input.slider( state, @@ -2334,6 +2371,7 @@ const img2imgTool = () => array.appendChild(state.ctxmenu.snapToGridLabel); array.appendChild(state.ctxmenu.invertMaskLabel); array.appendChild(state.ctxmenu.preserveMasksLabel); + array.appendChild(state.ctxmenu.removeBackgroundLabel); array.appendChild(state.ctxmenu.keepUnmaskedLabel); menu.appendChild(array); menu.appendChild(state.ctxmenu.keepUnmaskedBlurSlider); diff --git a/pages/configuration.html b/pages/configuration.html index 531147f..3f1aa2e 100644 --- a/pages/configuration.html +++ b/pages/configuration.html @@ -5,7 +5,7 @@ openOutpaint 🐠 - + diff --git a/res/icons/fish.svg b/res/icons/fish.svg new file mode 100644 index 0000000..c5f5b72 --- /dev/null +++ b/res/icons/fish.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/res/icons/image-minus.svg b/res/icons/image-minus.svg new file mode 100644 index 0000000..51eafa0 --- /dev/null +++ b/res/icons/image-minus.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/res/icons/scissors.svg b/res/icons/scissors.svg new file mode 100644 index 0000000..7c3526a --- /dev/null +++ b/res/icons/scissors.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/res/icons/slice.svg b/res/icons/slice.svg new file mode 100644 index 0000000..77284dc --- /dev/null +++ b/res/icons/slice.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file