diff --git a/.prettierrc.json b/.prettierrc.json new file mode 100644 index 0000000..c7dae26 --- /dev/null +++ b/.prettierrc.json @@ -0,0 +1,19 @@ +{ + "arrowParens": "always", + "bracketSameLine": true, + "bracketSpacing": false, + "embeddedLanguageFormatting": "auto", + "htmlWhitespaceSensitivity": "css", + "insertPragma": false, + "jsxSingleQuote": false, + "printWidth": 80, + "proseWrap": "preserve", + "quoteProps": "as-needed", + "requirePragma": false, + "semi": true, + "singleQuote": false, + "tabWidth": 2, + "trailingComma": "es5", + "useTabs": true, + "vueIndentScriptAndStyle": false + } \ No newline at end of file diff --git a/README.md b/README.md index 4069e6f..450c217 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,12 @@ this is a completely vanilla javascript and html canvas outpainting convenience - easily change samplers/steps/CFG/etc options for each dream summoned from the latent void - optional right-click to erase output image under cursor - optional grid snapping for precision - - optional overmasking for potentially better seams between outpaints and it _sorta_ works currently but it needs fixing - set overmask px value to 0 to disable the feature + - optional overmasking for potentially better seams between outpaints - set overmask px value to 0 to disable the feature - optional hi-res fix for blank/txt2img dreams which, if enabled, uses image width/height / 2 as firstpass size - import arbitrary images and superimpose on the canvas wherever you'd like ([extra fun with transparent .pngs!](#arbitrary_transparent)) - "temporary" monitors at the bottom to see exactly what mask/image you're feeding img2img, no i'm certainly not using them as actual imagedata sources or anything - saves your preferences to browser localstorage for maximum convenience + - undo/redo ## operation @@ -69,7 +70,7 @@ this is a completely vanilla javascript and html canvas outpainting convenience - [ ] lots and lots of readme updates (ongoing) - [ ] comment basically everything that isn't self documenting (ongoing) - [ ] CHORE: refactor all the duplicated JS code (ongoing, guaranteed to get worse before it gets better) -- [x] overmask seam of img2img - BUG: it kinda sucks currently, just moves the seam instead of fixing it, i want to try to gradient-fade the edge but filter = 'blur(Ypx)' is _awful_ for this and my remedial per-pixel loops crash the browser because i am the embodiment of inefficiency +- [x] overmask seam of img2img ~~BUG: it kinda sucks currently, just moves the seam instead of fixing it, i want to try to gradient-fade the edge but filter = 'blur(Ypx)' is awful for this and my remedial per-pixel loops crash the browser because i am the embodiment of inefficiency~~ - [x] split out CSS to its own file (remedial cleanup task) - [x] ability to blank/new canvas without making the user refresh the page because that's pretty janky - [ ] add error handling for async/XHR POST in case of, yknow, errors @@ -84,7 +85,7 @@ this is a completely vanilla javascript and html canvas outpainting convenience - [ ] discrete size control for mask and target reticle, discrete x/y axes for reticle - [x] ~~floating/togglable menu leftnav bar with categorized/sensibly laid-out options~~ - [ ] infinite canvas -- [ ] global undo/redo +- [x] ~~global undo/redo~~ - [ ] inpainting sketch tools - [ ] split out JS to separation-of-concerns individual files (oh no) - [x] ~~something actually similar to a "user interface", preferably visually pleasant and would make my mom say "well that makes sense" if she looked at it~~ @@ -138,12 +139,15 @@ imported a transparent clip of a [relatively famous happy lil kitty](https://com - 0.0.5.5 - highly attractive and functional floating control panel which will be extremely useful for infinite canvas [dac188d](https://github.com/zero01101/openOutpaint/commit/dac188dbfb086d3063f14b1a6a6a5b3add1aa5f5) - 0.0.5.6 - _FINALLY_ the sliders update their values in realtime, a nice overall start on cleaning up my mess [d9fb87a](https://github.com/zero01101/openOutpaint/commit/d9fb87acec6653f19a9dac7777bd866782303ebc) - 0.0.5.7 - the majestic return of mask erasing, removed unnecessary overmask toggle [a96fd11](https://github.com/zero01101/openOutpaint/commit/a96fd116d750e38ce8982104ae5e5c966746fdc4) +- 0.0.6 - absolutely brilliant undo/redo system, logical and straightforward enough to the point where even i can understand what it's doing [25681b3](https://github.com/zero01101/openOutpaint/commit/25681b3a83bbd7a1d1b3e675f26f141692d77c79) +- 0.0.6.1 - finally think i've got overmasking working better with a bit of "humanization" to the automated masks, please play around with it and see if it's any better or just sucks in general [8002772](https://github.com/zero01101/openOutpaint/commit/8002772ee6aa4b2f5b544af82cb6d545cf81368f) ## collaborator credits 👑 - [@jasonmhead](https://github.com/jasonmhead) - [the most minimal launch script](https://github.com/zero01101/openOutpaint/pull/1) - [@Kalekki](https://github.com/Kalekki) - all SORTS of awesome goodness, legit pull request hero: [what i was calling "smart crop"](https://github.com/zero01101/openOutpaint/pull/2), [localstorage](https://github.com/zero01101/openOutpaint/pull/5), [right-click erase](https://github.com/zero01101/openOutpaint/pull/7), [delightful floating UI](https://github.com/zero01101/openOutpaint/pull/11), [mask erase fix](https://github.com/zero01101/openOutpaint/pull/17) - - [@seijihariki](https://github.com/seijihariki) - realtime slider value updates, gracious code cleanup ([14](https://github.com/zero01101/openOutpaint/pull/14)) - - [@lifeh2o](https://www.reddit.com/user/lifeh2o/overview) - overmasking concept that is still driving me crazy getting it to work right ([a](https://www.reddit.com/r/StableDiffusion/comments/ywf8np/i_made_a_completely_local_offline_opensource/iwl6s06/),[b](https://www.reddit.com/r/StableDiffusion/comments/ys9lhq/kollai_an_infinite_multiuser_canvas_running_on/ivzygwk/?context=3)) + - [@seijihariki](https://github.com/seijihariki) - [realtime slider value updates, gracious code cleanup](https://github.com/zero01101/openOutpaint/pull/14), [blessed undo/redo](https://github.com/zero01101/openOutpaint/pull/21) + - [@lifeh2o](https://www.reddit.com/user/lifeh2o/overview) - overmasking concept ~~that is still driving me crazy getting it to work right~~ ([a](https://www.reddit.com/r/StableDiffusion/comments/ywf8np/i_made_a_completely_local_offline_opensource/iwl6s06/),[b](https://www.reddit.com/r/StableDiffusion/comments/ys9lhq/kollai_an_infinite_multiuser_canvas_running_on/ivzygwk/?context=3)) [possible betterness?](https://github.com/zero01101/openOutpaint/commit/8002772ee6aa4b2f5b544af82cb6d545cf81368f) + ## what's with the fish? deep aquatic life is _fascinating_ so i went with something underwater for a default prompt which led to making an _"illustration of a bright orange fish, plain blue solid background"_ favicon which led to "ok then, fish is mascot" diff --git a/index.html b/index.html index a8e525c..bbaa0ec 100644 --- a/index.html +++ b/index.html @@ -109,7 +109,7 @@

- Alpha release v0.0.5.7 + Alpha release v0.0.6.1

diff --git a/js/commands.js b/js/commands.js index 5cf6854..f5594f8 100644 --- a/js/commands.js +++ b/js/commands.js @@ -2,131 +2,124 @@ * Command pattern to allow for editing history */ const commands = { - current: -1, - history: [], - undo(n = 1) { - for (var i = 0; i < n && this.current > -1; i++) { - this.history[this.current--].undo(); - } - }, - redo(n = 1) { - for (var i = 0; i < n && this.current + 1 < this.history.length; i++) { - this.history[++this.current].redo(); - } - }, + current: -1, + history: [], + undo(n = 1) { + for (var i = 0; i < n && this.current > -1; i++) { + this.history[this.current--].undo(); + } + }, + redo(n = 1) { + for (var i = 0; i < n && this.current + 1 < this.history.length; i++) { + this.history[++this.current].redo(); + } + }, - /** - * These are basic commands that can be done/undone - * - * They must contain a 'run' method that performs the action the first time, - * a 'undo' method that undoes that action and a 'redo' method that does the - * action again, but without requiring parameters. 'redo' is by default the - * same as 'run'. - * - * The 'run' and 'redo' functions will receive a 'options' parameter which will be - * forwarded directly to the operation, and a 'state' parameter that - * can be used to store state for undoing things. - * - * The 'state' object will be passed to the 'undo' function as well. - */ - createCommand(name, run, undo, redo = run) { - const command = function runWrapper(options) { - // Create copy of options and state object - const copy = {}; - Object.assign(copy, options); - const state = {}; + /** + * These are basic commands that can be done/undone + * + * They must contain a 'run' method that performs the action the first time, + * a 'undo' method that undoes that action and a 'redo' method that does the + * action again, but without requiring parameters. 'redo' is by default the + * same as 'run'. + * + * The 'run' and 'redo' functions will receive a 'options' parameter which will be + * forwarded directly to the operation, and a 'state' parameter that + * can be used to store state for undoing things. + * + * The 'state' object will be passed to the 'undo' function as well. + */ + createCommand(name, run, undo, redo = run) { + const command = function runWrapper(options) { + // Create copy of options and state object + const copy = {}; + Object.assign(copy, options); + const state = {}; - // Attempt to run command - try { - run(copy, state); - } catch (e) { - console.warn( - `Error while running command '${name}' with options:` - ); - console.warn(copy); - console.warn(e); - return; - } + // Attempt to run command + try { + run(copy, state); + } catch (e) { + console.warn(`Error while running command '${name}' with options:`); + console.warn(copy); + console.warn(e); + return; + } - const undoWrapper = () => { - console.debug(`Undoing ${name}, currently ${commands.current}`); - undo(state); - }; - const redoWrapper = () => { - console.debug(`Redoing ${name}, currently ${commands.current}`); - redo(copy, state); - }; + const undoWrapper = () => { + console.debug(`Undoing ${name}, currently ${commands.current}`); + undo(state); + }; + const redoWrapper = () => { + console.debug(`Redoing ${name}, currently ${commands.current}`); + redo(copy, state); + }; - // Add to history - if (commands.history.length > commands.current + 1) - commands.history.splice(commands.current + 1); - commands.history.push({ undo: undoWrapper, redo: redoWrapper }); - commands.current++; - }; + // Add to history + if (commands.history.length > commands.current + 1) + commands.history.splice(commands.current + 1); + commands.history.push({undo: undoWrapper, redo: redoWrapper}); + commands.current++; + }; - this.types[name] = command; + this.types[name] = command; - return command; - }, - runCommand(name, options) { - this.types[name](options); - }, - types: {}, + return command; + }, + runCommand(name, options) { + this.types[name](options); + }, + types: {}, }; /** * Draw Image Command, used to draw a Image to a context */ commands.createCommand( - "drawImage", - (options, state) => { - if ( - !options || - options.image === undefined || - options.x === undefined || - options.y === undefined - ) - throw "Command drawImage requires options in the format: {image, x, y, ctx?}"; + "drawImage", + (options, state) => { + if ( + !options || + options.image === undefined || + options.x === undefined || + options.y === undefined + ) + throw "Command drawImage requires options in the format: {image, x, y, ctx?}"; - // Check if we have state - if (!state.context) { - const context = options.ctx || imgCtx; - state.context = context; + // Check if we have state + if (!state.context) { + const context = options.ctx || imgCtx; + state.context = context; - // Saving what was in the canvas before the command - const imgData = context.getImageData( - options.x, - options.y, - options.image.width, - options.image.height - ); - state.box = { - x: options.x, - y: options.y, - w: options.image.width, - h: options.image.height, - }; - // Create Image - const cutout = document.createElement("canvas"); - cutout.width = state.box.w; - cutout.height = state.box.h; - cutout.getContext("2d").putImageData(imgData, 0, 0); - state.original = new Image(); - state.original.src = cutout.toDataURL(); - } + // Saving what was in the canvas before the command + const imgData = context.getImageData( + options.x, + options.y, + options.image.width, + options.image.height + ); + state.box = { + x: options.x, + y: options.y, + w: options.image.width, + h: options.image.height, + }; + // Create Image + const cutout = document.createElement("canvas"); + cutout.width = state.box.w; + cutout.height = state.box.h; + cutout.getContext("2d").putImageData(imgData, 0, 0); + state.original = new Image(); + state.original.src = cutout.toDataURL(); + } - // Apply command - state.context.drawImage(options.image, state.box.x, state.box.y); - }, - (state) => { - // Clear destination area - state.context.clearRect( - state.box.x, - state.box.y, - state.box.w, - state.box.h - ); - // Undo - state.context.drawImage(state.original, state.box.x, state.box.y); - } + // Apply command + state.context.drawImage(options.image, state.box.x, state.box.y); + }, + (state) => { + // Clear destination area + state.context.clearRect(state.box.x, state.box.y, state.box.w, state.box.h); + // Undo + state.context.drawImage(state.original, state.box.x, state.box.y); + } ); diff --git a/js/index.js b/js/index.js index 82d01ea..c939f30 100644 --- a/js/index.js +++ b/js/index.js @@ -4,84 +4,84 @@ window.onload = startup; var stableDiffusionData = { - //includes img2img data but works for txt2img just fine - prompt: '', - negative_prompt: '', - seed: -1, - cfg_scale: null, - sampler_index: 'DDIM', - steps: null, - denoising_strength: 1, - mask_blur: 0, - batch_size: null, - width: 512, - height: 512, - n_iter: null, // batch count - mask: '', - init_images: [], - inpaint_full_res: false, - inpainting_fill: 2, - enable_hr: false, - firstphase_width: 0, - firstphase_height: 0, - // here's some more fields that might be useful + //includes img2img data but works for txt2img just fine + prompt: "", + negative_prompt: "", + seed: -1, + cfg_scale: null, + sampler_index: "DDIM", + steps: null, + denoising_strength: 1, + mask_blur: 0, + batch_size: null, + width: 512, + height: 512, + n_iter: null, // batch count + mask: "", + init_images: [], + inpaint_full_res: false, + inpainting_fill: 2, + enable_hr: false, + firstphase_width: 0, + firstphase_height: 0, + // here's some more fields that might be useful - // ---txt2img specific: - // "enable_hr": false, // hires fix - // "denoising_strength": 0, // ok this is in both txt and img2img but txt2img only applies it if enable_hr == true - // "firstphase_width": 0, // hires fp w - // "firstphase_height": 0, // see above s/w/h/ + // ---txt2img specific: + // "enable_hr": false, // hires fix + // "denoising_strength": 0, // ok this is in both txt and img2img but txt2img only applies it if enable_hr == true + // "firstphase_width": 0, // hires fp w + // "firstphase_height": 0, // see above s/w/h/ - // ---img2img specific - // "init_images": [ // imageS!??!? wtf maybe for batch img2img?? i just dump one base64 in here - // "string" - // ], - // "resize_mode": 0, - // "denoising_strength": 0.75, // yeah see - // "mask": "string", // string is just a base64 image - // "mask_blur": 4, - // "inpainting_fill": 0, // 0- fill, 1- orig, 2- latent noise, 3- latent nothing - // "inpaint_full_res": true, - // "inpaint_full_res_padding": 0, // px - // "inpainting_mask_invert": 0, // bool??????? wtf - // "include_init_images": false // ?????? + // ---img2img specific + // "init_images": [ // imageS!??!? wtf maybe for batch img2img?? i just dump one base64 in here + // "string" + // ], + // "resize_mode": 0, + // "denoising_strength": 0.75, // yeah see + // "mask": "string", // string is just a base64 image + // "mask_blur": 4, + // "inpainting_fill": 0, // 0- fill, 1- orig, 2- latent noise, 3- latent nothing + // "inpaint_full_res": true, + // "inpaint_full_res_padding": 0, // px + // "inpainting_mask_invert": 0, // bool??????? wtf + // "include_init_images": false // ?????? }; /** * Some Utility Functions */ function sliderChangeHandlerFactory( - sliderId, - textBoxId, - dataKey, - defaultV, - setter = (k, v) => (stableDiffusionData[k] = v), - getter = (k) => stableDiffusionData[k] + sliderId, + textBoxId, + dataKey, + defaultV, + setter = (k, v) => (stableDiffusionData[k] = v), + getter = (k) => stableDiffusionData[k] ) { - const sliderEl = document.getElementById(sliderId); - const textBoxEl = document.getElementById(textBoxId); - const savedValue = localStorage.getItem(dataKey); + const sliderEl = document.getElementById(sliderId); + const textBoxEl = document.getElementById(textBoxId); + const savedValue = localStorage.getItem(dataKey); - if (savedValue) setter(dataKey, savedValue || defaultV); + if (savedValue) setter(dataKey, savedValue || defaultV); - function changeHandler(evn) { - const eventSource = evn && evn.srcElement; - const value = eventSource && Number(eventSource.value); + function changeHandler(evn) { + const eventSource = evn && evn.srcElement; + const value = eventSource && Number(eventSource.value); - if (value) setter(dataKey, value); + if (value) setter(dataKey, value); - if (!eventSource || eventSource.id === textBoxId) - sliderEl.value = getter(dataKey); - setter(dataKey, Number(sliderEl.value)); - textBoxEl.value = getter(dataKey); + if (!eventSource || eventSource.id === textBoxId) + sliderEl.value = getter(dataKey); + setter(dataKey, Number(sliderEl.value)); + textBoxEl.value = getter(dataKey); - localStorage.setItem(dataKey, getter(dataKey)); - } + localStorage.setItem(dataKey, getter(dataKey)); + } - textBoxEl.onchange = changeHandler; - sliderEl.oninput = changeHandler; + textBoxEl.onchange = changeHandler; + sliderEl.oninput = changeHandler; - return changeHandler; + return changeHandler; } // stuff things use @@ -89,9 +89,9 @@ var blockNewImages = false; var returnedImages; var imageIndex = 0; var tmpImgXYWH = {}; -var host = ''; -var url = '/sdapi/v1/'; -var endpoint = 'txt2img'; +var host = ""; +var url = "/sdapi/v1/"; +var endpoint = "txt2img"; var frameX = 512; var frameY = 512; var prevMouseX = 0; @@ -124,1019 +124,858 @@ var arbitraryImageBitmap; var arbitraryImageBase64; // seriously js cmon work with me here var placingArbitraryImage = false; // for when the user has loaded an existing image from their computer var enableErasing = false; // accidental right-click erase if the user isn't trying to erase is a bad thing +var marchOffset = 0; +var marching = false; +var marchCoords = {}; // info div, sometimes hidden -let mouseXInfo = document.getElementById('mouseX'); -let mouseYInfo = document.getElementById('mouseY'); -let canvasXInfo = document.getElementById('canvasX'); -let canvasYInfo = document.getElementById('canvasY'); -let snapXInfo = document.getElementById('snapX'); -let snapYInfo = document.getElementById('snapY'); -let heldButtonInfo = document.getElementById('heldButton'); +let mouseXInfo = document.getElementById("mouseX"); +let mouseYInfo = document.getElementById("mouseY"); +let canvasXInfo = document.getElementById("canvasX"); +let canvasYInfo = document.getElementById("canvasY"); +let snapXInfo = document.getElementById("snapX"); +let snapYInfo = document.getElementById("snapY"); +let heldButtonInfo = document.getElementById("heldButton"); // canvases and related -const ovCanvas = document.getElementById('overlayCanvas'); // where mouse cursor renders -const ovCtx = ovCanvas.getContext('2d'); -const tgtCanvas = document.getElementById('targetCanvas'); // where "box" gets drawn before dream happens -const tgtCtx = tgtCanvas.getContext('2d'); -const maskPaintCanvas = document.getElementById('maskPaintCanvas'); // where masking brush gets painted -const maskPaintCtx = maskPaintCanvas.getContext('2d'); -const tempCanvas = document.getElementById('tempCanvas'); // where select/rejects get superimposed temporarily -const tempCtx = tempCanvas.getContext('2d'); -const imgCanvas = document.getElementById('canvas'); // where dreams go -const imgCtx = imgCanvas.getContext('2d'); -const bgCanvas = document.getElementById('backgroundCanvas'); // gray bg grid -const bgCtx = bgCanvas.getContext('2d'); +const ovCanvas = document.getElementById("overlayCanvas"); // where mouse cursor renders +const ovCtx = ovCanvas.getContext("2d"); +const tgtCanvas = document.getElementById("targetCanvas"); // where "box" gets drawn before dream happens +const tgtCtx = tgtCanvas.getContext("2d"); +const maskPaintCanvas = document.getElementById("maskPaintCanvas"); // where masking brush gets painted +const maskPaintCtx = maskPaintCanvas.getContext("2d"); +const tempCanvas = document.getElementById("tempCanvas"); // where select/rejects get superimposed temporarily +const tempCtx = tempCanvas.getContext("2d"); +const imgCanvas = document.getElementById("canvas"); // where dreams go +const imgCtx = imgCanvas.getContext("2d"); +const bgCanvas = document.getElementById("backgroundCanvas"); // gray bg grid +const bgCtx = bgCanvas.getContext("2d"); function startup() { - checkIfWebuiIsRunning(); - loadSettings(); - drawBackground(); - changeScaleFactor(); - changePaintMode(); - changeSampler(); - changeSteps(); - changeCfgScale(); - changeBatchCount(); - changeBatchSize(); - changeSnapMode(); - changeMaskBlur(); - changeSeed(); - changeOverMaskPx(); - changeHiResFix(); - changeEnableErasing(); - document.getElementById('overlayCanvas').onmousemove = mouseMove; - document.getElementById('overlayCanvas').onmousedown = mouseDown; - document.getElementById('overlayCanvas').onmouseup = mouseUp; - document.getElementById('scaleFactor').value = scaleFactor; + checkIfWebuiIsRunning(); + loadSettings(); + drawBackground(); + changeScaleFactor(); + changePaintMode(); + changeSampler(); + changeSteps(); + changeCfgScale(); + changeBatchCount(); + changeBatchSize(); + changeSnapMode(); + changeMaskBlur(); + changeSeed(); + changeOverMaskPx(); + changeHiResFix(); + changeEnableErasing(); + document.getElementById("overlayCanvas").onmousemove = mouseMove; + document.getElementById("overlayCanvas").onmousedown = mouseDown; + document.getElementById("overlayCanvas").onmouseup = mouseUp; + document.getElementById("scaleFactor").value = scaleFactor; } function drop(imageParams) { - const img = new Image(); - img.onload = function () { - writeArbitraryImage(img, imageParams.x, imageParams.y); - }; - img.src = arbitraryImageBase64; + const img = new Image(); + img.onload = function () { + writeArbitraryImage(img, imageParams.x, imageParams.y); + }; + img.src = arbitraryImageBase64; } function writeArbitraryImage(img, x, y) { - commands.runCommand('drawImage', { - x, - y, - image: img, - }); - blockNewImages = false; - placingArbitraryImage = false; - document.getElementById('preloadImage').files = null; + commands.runCommand("drawImage", { + x, + y, + image: img, + }); + blockNewImages = false; + placingArbitraryImage = false; + document.getElementById("preloadImage").files = null; } function dream(x, y, prompt) { - tmpImgXYWH.x = x; - tmpImgXYWH.y = y; - tmpImgXYWH.w = prompt.width; - tmpImgXYWH.h = prompt.height; - console.log( - 'dreaming to ' + - host + - url + - endpoint + - ':\r\n' + - JSON.stringify(prompt) - ); - postData(prompt).then((data) => { - returnedImages = data.images; - totalImagesReturned = data.images.length; - blockNewImages = true; - //console.log(data); // JSON data parsed by `data.json()` call - imageAcceptReject(x, y, data); - }); + tmpImgXYWH.x = x; + tmpImgXYWH.y = y; + tmpImgXYWH.w = prompt.width; + tmpImgXYWH.h = prompt.height; + console.log( + "dreaming to " + host + url + endpoint + ":\r\n" + JSON.stringify(prompt) + ); + postData(prompt).then((data) => { + returnedImages = data.images; + totalImagesReturned = data.images.length; + blockNewImages = true; + //console.log(data); // JSON data parsed by `data.json()` call + imageAcceptReject(x, y, data); + }); } async function postData(promptData) { - this.host = document.getElementById('host').value; - // Default options are marked with * - const response = await fetch(this.host + this.url + this.endpoint, { - method: 'POST', // *GET, POST, PUT, DELETE, etc. - mode: 'cors', // no-cors, *cors, same-origin - cache: 'default', // *default, no-cache, reload, force-cache, only-if-cached - credentials: 'same-origin', // include, *same-origin, omit - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - redirect: 'follow', // manual, *follow, error - referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url - body: JSON.stringify(promptData), // body data type must match "Content-Type" header - }); - return response.json(); // parses JSON response into native JavaScript objects + this.host = document.getElementById("host").value; + // Default options are marked with * + const response = await fetch(this.host + this.url + this.endpoint, { + method: "POST", // *GET, POST, PUT, DELETE, etc. + mode: "cors", // no-cors, *cors, same-origin + cache: "default", // *default, no-cache, reload, force-cache, only-if-cached + credentials: "same-origin", // include, *same-origin, omit + headers: { + Accept: "application/json", + "Content-Type": "application/json", + }, + redirect: "follow", // manual, *follow, error + referrerPolicy: "no-referrer", // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url + body: JSON.stringify(promptData), // body data type must match "Content-Type" header + }); + return response.json(); // parses JSON response into native JavaScript objects } function imageAcceptReject(x, y, data) { - const img = new Image(); - img.onload = function () { - tempCtx.drawImage(img, x, y); //imgCtx for actual image, tmp for... holding? - var div = document.createElement('div'); - div.id = 'veryTempDiv'; - div.style.position = 'absolute'; - div.style.left = parseInt(x) + 'px'; - div.style.top = parseInt(y + data.parameters.height) + 'px'; - div.style.width = '150px'; - div.style.height = '50px'; - div.innerHTML = - ' of '; - document.getElementById('tempDiv').appendChild(div); - document.getElementById('currentImgIndex').innerText = '1'; - document.getElementById('totalImgIndex').innerText = - totalImagesReturned; - }; - // set the image displayed as the first regardless of batch size/count - imageIndex = 0; - // load the image data after defining the closure - img.src = 'data:image/png;base64,' + returnedImages[imageIndex]; + const img = new Image(); + img.onload = function () { + tempCtx.drawImage(img, x, y); //imgCtx for actual image, tmp for... holding? + var div = document.createElement("div"); + div.id = "veryTempDiv"; + div.style.position = "absolute"; + div.style.left = parseInt(x) + "px"; + div.style.top = parseInt(y + data.parameters.height) + "px"; + div.style.width = "150px"; + div.style.height = "50px"; + div.innerHTML = + ' of '; + document.getElementById("tempDiv").appendChild(div); + document.getElementById("currentImgIndex").innerText = "1"; + document.getElementById("totalImgIndex").innerText = totalImagesReturned; + }; + // set the image displayed as the first regardless of batch size/count + imageIndex = 0; + // load the image data after defining the closure + img.src = "data:image/png;base64," + returnedImages[imageIndex]; } function accept(evt) { - // write image to imgcanvas - clearBackupMask(); - placeImage(); - removeChoiceButtons(); - clearTargetMask(); - blockNewImages = false; + // write image to imgcanvas + marching = false; + clearBackupMask(); + placeImage(); + removeChoiceButtons(); + clearTargetMask(); + blockNewImages = false; } function reject(evt) { - // remove image entirely - restoreBackupMask(); - clearBackupMask(); - clearTargetMask(); - removeChoiceButtons(); - blockNewImages = false; + // remove image entirely + marching = false; + restoreBackupMask(); + clearBackupMask(); + clearTargetMask(); + removeChoiceButtons(); + blockNewImages = false; } function newImage(evt) { - clearPaintedMask(); - clearBackupMask(); - clearTargetMask(); - clearImgMask(); + clearPaintedMask(); + clearBackupMask(); + clearTargetMask(); + clearImgMask(); } function prevImg(evt) { - if (imageIndex == 0) { - imageIndex = totalImagesReturned; - } - changeImg(false); + if (imageIndex == 0) { + imageIndex = totalImagesReturned; + } + changeImg(false); } function nextImg(evt) { - if (imageIndex == totalImagesReturned - 1) { - imageIndex = -1; - } - changeImg(true); + if (imageIndex == totalImagesReturned - 1) { + imageIndex = -1; + } + changeImg(true); } function changeImg(forward) { - const img = new Image(); - tempCtx.clearRect(0, 0, tempCtx.width, tempCtx.height); - img.onload = function () { - tempCtx.drawImage(img, tmpImgXYWH.x, tmpImgXYWH.y); //imgCtx for actual image, tmp for... holding? - }; - var tmpIndex = document.getElementById('currentImgIndex'); - if (forward) { - imageIndex++; - } else { - imageIndex--; - } - tmpIndex.innerText = imageIndex + 1; - // load the image data after defining the closure - img.src = 'data:image/png;base64,' + returnedImages[imageIndex]; //TODO need way to dream batches and select from results + const img = new Image(); + tempCtx.clearRect(0, 0, tempCtx.width, tempCtx.height); + img.onload = function () { + tempCtx.drawImage(img, tmpImgXYWH.x, tmpImgXYWH.y); //imgCtx for actual image, tmp for... holding? + }; + var tmpIndex = document.getElementById("currentImgIndex"); + if (forward) { + imageIndex++; + } else { + imageIndex--; + } + tmpIndex.innerText = imageIndex + 1; + // load the image data after defining the closure + img.src = "data:image/png;base64," + returnedImages[imageIndex]; //TODO need way to dream batches and select from results } function removeChoiceButtons(evt) { - const element = document.getElementById('veryTempDiv'); - element.remove(); - tempCtx.clearRect(0, 0, tempCanvas.width, tempCanvas.height); + const element = document.getElementById("veryTempDiv"); + element.remove(); + tempCtx.clearRect(0, 0, tempCanvas.width, tempCanvas.height); } function restoreBackupMask() { - // reapply mask if exists - if (backupMaskChunk != null && backupMaskX != null && backupMaskY != null) { - // backup mask data exists - var iData = new ImageData( - backupMaskChunk.data, - backupMaskChunk.height, - backupMaskChunk.width - ); - maskPaintCtx.putImageData(iData, backupMaskX, backupMaskY); - } + // reapply mask if exists + if (backupMaskChunk != null && backupMaskX != null && backupMaskY != null) { + // backup mask data exists + var iData = new ImageData( + backupMaskChunk.data, + backupMaskChunk.height, + backupMaskChunk.width + ); + maskPaintCtx.putImageData(iData, backupMaskX, backupMaskY); + } } function clearBackupMask() { - // clear backupmask - backupMaskChunk = null; - backupMaskX = null; - backupMaskY = null; + // clear backupmask + backupMaskChunk = null; + backupMaskX = null; + backupMaskY = null; } function clearTargetMask() { - tgtCtx.clearRect(0, 0, tgtCanvas.width, tgtCanvas.height); + tgtCtx.clearRect(0, 0, tgtCanvas.width, tgtCanvas.height); } function clearImgMask() { - imgCtx.clearRect(0, 0, imgCanvas.width, imgCanvas.height); + imgCtx.clearRect(0, 0, imgCanvas.width, imgCanvas.height); } function clearPaintedMask() { - maskPaintCtx.clearRect(0, 0, maskPaintCanvas.width, maskPaintCanvas.height); + maskPaintCtx.clearRect(0, 0, maskPaintCanvas.width, maskPaintCanvas.height); } function placeImage() { - const img = new Image(); - img.onload = function () { - commands.runCommand('drawImage', { - x: tmpImgXYWH.x, - y: tmpImgXYWH.y, - image: img, - }); - tmpImgXYWH = {}; - returnedImages = null; - }; - // load the image data after defining the closure - img.src = 'data:image/png;base64,' + returnedImages[imageIndex]; + const img = new Image(); + img.onload = function () { + commands.runCommand("drawImage", { + x: tmpImgXYWH.x, + y: tmpImgXYWH.y, + image: img, + }); + tmpImgXYWH = {}; + returnedImages = null; + }; + // load the image data after defining the closure + img.src = "data:image/png;base64," + returnedImages[imageIndex]; } function sleep(ms) { - // what was this even for, anyway? - return new Promise((resolve) => setTimeout(resolve, ms)); + // what was this even for, anyway? + return new Promise((resolve) => setTimeout(resolve, ms)); } function snap(i, scaled = true) { - // very cheap test proof of concept but it works surprisingly well - var scaleOffset = 0; - if (scaled) { - if (scaleFactor % 2 != 0) { - // odd number, snaps to center of cell, oops - scaleOffset = basePixelCount / 2; - } - } - var snapOffset = (i % basePixelCount) - scaleOffset; - if (snapOffset == 0) { - return snapOffset; - } - return -snapOffset; + // very cheap test proof of concept but it works surprisingly well + var scaleOffset = 0; + if (scaled) { + if (scaleFactor % 2 != 0) { + // odd number, snaps to center of cell, oops + scaleOffset = basePixelCount / 2; + } + } + var snapOffset = (i % basePixelCount) - scaleOffset; + if (snapOffset == 0) { + return snapOffset; + } + return -snapOffset; +} + +function march() { + if (marching) { + marchOffset++; + if (marchOffset > 16) { + marchOffset = 0; + } + drawMarchingAnts(); + setTimeout(march, 20); + } +} + +function drawMarchingAnts() { + clearTargetMask(); + tgtCtx.strokeStyle = "#333333FF"; //"#55000077"; + tgtCtx.setLineDash([4, 2]); + tgtCtx.lineDashOffset = -marchOffset; + tgtCtx.strokeRect(marchCoords.x, marchCoords.y, marchCoords.w, marchCoords.h); } function mouseMove(evt) { - const rect = ovCanvas.getBoundingClientRect(); // not-quite pixel offset was driving me insane - const canvasOffsetX = rect.left; - const canvasOffsetY = rect.top; - heldButton = evt.buttons; - mouseXInfo.innerText = mouseX = evt.clientX; - mouseYInfo.innerText = mouseY = evt.clientY; - canvasXInfo.innerText = canvasX = parseInt(evt.clientX - rect.left); - canvasYInfo.innerText = canvasY = parseInt(evt.clientY - rect.top); - snapXInfo.innerText = canvasX + snap(canvasX); - snapYInfo.innerText = canvasY + snap(canvasY); - heldButtonInfo.innerText = heldButton; - ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); // clear out the previous mouse cursor - if (placingArbitraryImage) { - // ugh refactor so this isn't duplicated between arbitrary image and dream reticle modes - snapOffsetX = 0; - snapOffsetY = 0; - if (snapToGrid) { - snapOffsetX = snap(canvasX, false); - snapOffsetY = snap(canvasY, false); - } - finalX = snapOffsetX + canvasX; - finalY = snapOffsetY + canvasY; - ovCtx.drawImage(arbitraryImage, finalX, finalY); - } else if (!paintMode) { - // draw targeting square reticle thingy cursor - ovCtx.strokeStyle = '#00000077'; - snapOffsetX = 0; - snapOffsetY = 0; - if (snapToGrid) { - snapOffsetX = snap(canvasX); - snapOffsetY = snap(canvasY); - } - finalX = snapOffsetX + canvasX; - finalY = snapOffsetY + canvasY; - ovCtx.strokeRect( - parseInt(finalX - (basePixelCount * scaleFactor) / 2), - parseInt(finalY - (basePixelCount * scaleFactor) / 2), - basePixelCount * scaleFactor, - basePixelCount * scaleFactor - ); //origin is middle of the frame - } + const rect = ovCanvas.getBoundingClientRect(); // not-quite pixel offset was driving me insane + const canvasOffsetX = rect.left; + const canvasOffsetY = rect.top; + heldButton = evt.buttons; + mouseXInfo.innerText = mouseX = evt.clientX; + mouseYInfo.innerText = mouseY = evt.clientY; + canvasXInfo.innerText = canvasX = parseInt(evt.clientX - rect.left); + canvasYInfo.innerText = canvasY = parseInt(evt.clientY - rect.top); + snapXInfo.innerText = canvasX + snap(canvasX); + snapYInfo.innerText = canvasY + snap(canvasY); + heldButtonInfo.innerText = heldButton; + ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); // clear out the previous mouse cursor + if (placingArbitraryImage) { + // ugh refactor so this isn't duplicated between arbitrary image and dream reticle modes + snapOffsetX = 0; + snapOffsetY = 0; + if (snapToGrid) { + snapOffsetX = snap(canvasX, false); + snapOffsetY = snap(canvasY, false); + } + finalX = snapOffsetX + canvasX; + finalY = snapOffsetY + canvasY; + ovCtx.drawImage(arbitraryImage, finalX, finalY); + } else if (!paintMode) { + // draw targeting square reticle thingy cursor + ovCtx.strokeStyle = "#00000077"; + snapOffsetX = 0; + snapOffsetY = 0; + if (snapToGrid) { + snapOffsetX = snap(canvasX); + snapOffsetY = snap(canvasY); + } + finalX = snapOffsetX + canvasX; + finalY = snapOffsetY + canvasY; + ovCtx.strokeRect( + parseInt(finalX - (basePixelCount * scaleFactor) / 2), + parseInt(finalY - (basePixelCount * scaleFactor) / 2), + basePixelCount * scaleFactor, + basePixelCount * scaleFactor + ); //origin is middle of the frame + } } /** * Mask implementation */ mouse.listen.canvas.onmousemove.on((evn) => { - if (paintMode && evn.target.id === 'overlayCanvas') { - // draw big translucent red blob cursor - ovCtx.beginPath(); - ovCtx.arc(evn.x, evn.y, 4 * scaleFactor, 0, 2 * Math.PI, true); // for some reason 4x on an arc is === to 8x on a line??? - ovCtx.fillStyle = '#FF6A6A50'; - ovCtx.fill(); - } + if (paintMode && evn.target.id === "overlayCanvas") { + // draw big translucent red blob cursor + ovCtx.beginPath(); + ovCtx.arc(evn.x, evn.y, 4 * scaleFactor, 0, 2 * Math.PI, true); // for some reason 4x on an arc is === to 8x on a line??? + ovCtx.fillStyle = "#FF6A6A50"; + ovCtx.fill(); + } }); mouse.listen.canvas.left.onpaint.on((evn) => { - if (paintMode && evn.target.id === 'overlayCanvas') { - maskPaintCtx.globalCompositeOperation = 'source-over'; - maskPaintCtx.strokeStyle = '#FF6A6A'; + if (paintMode && evn.target.id === "overlayCanvas") { + maskPaintCtx.globalCompositeOperation = "source-over"; + maskPaintCtx.strokeStyle = "#FF6A6A"; - maskPaintCtx.lineWidth = 8 * scaleFactor; - maskPaintCtx.beginPath(); - maskPaintCtx.moveTo(evn.px, evn.py); - maskPaintCtx.lineTo(evn.x, evn.y); - maskPaintCtx.lineJoin = maskPaintCtx.lineCap = 'round'; - maskPaintCtx.stroke(); - } + maskPaintCtx.lineWidth = 8 * scaleFactor; + maskPaintCtx.beginPath(); + maskPaintCtx.moveTo(evn.px, evn.py); + maskPaintCtx.lineTo(evn.x, evn.y); + maskPaintCtx.lineJoin = maskPaintCtx.lineCap = "round"; + maskPaintCtx.stroke(); + } }); mouse.listen.canvas.right.onpaint.on((evn) => { - if (paintMode && evn.target.id === 'overlayCanvas') { - maskPaintCtx.globalCompositeOperation = 'destination-out'; - maskPaintCtx.strokeStyle = '#FFFFFFFF'; + if (paintMode && evn.target.id === "overlayCanvas") { + maskPaintCtx.globalCompositeOperation = "destination-out"; + maskPaintCtx.strokeStyle = "#FFFFFFFF"; - maskPaintCtx.lineWidth = 8 * scaleFactor; - maskPaintCtx.beginPath(); - maskPaintCtx.moveTo(evn.px, evn.py); - maskPaintCtx.lineTo(evn.x, evn.y); - maskPaintCtx.lineJoin = maskPaintCtx.lineCap = 'round'; - maskPaintCtx.stroke(); - } + maskPaintCtx.lineWidth = 8 * scaleFactor; + maskPaintCtx.beginPath(); + maskPaintCtx.moveTo(evn.px, evn.py); + maskPaintCtx.lineTo(evn.x, evn.y); + maskPaintCtx.lineJoin = maskPaintCtx.lineCap = "round"; + maskPaintCtx.stroke(); + } }); function mouseDown(evt) { - const rect = ovCanvas.getBoundingClientRect(); - var oddOffset = 0; - if (scaleFactor % 2 != 0) { - oddOffset = basePixelCount / 2; - } - if (evt.button == 0) { - // left click - if (placingArbitraryImage) { - var nextBox = {}; - nextBox.x = evt.offsetX; - nextBox.y = evt.offsetY; - nextBox.w = arbitraryImageData.width; - nextBox.h = arbitraryImageData.height; - dropTargets.push(nextBox); - } else if (!paintMode) { - //const rect = ovCanvas.getBoundingClientRect() - var nextBox = {}; - nextBox.x = - evt.clientX - - (basePixelCount * scaleFactor) / 2 - - rect.left + - oddOffset; //origin is middle of the frame - nextBox.y = - evt.clientY - - (basePixelCount * scaleFactor) / 2 - - rect.top + - oddOffset; //TODO make a way to set the origin to numpad dirs? - nextBox.w = basePixelCount * scaleFactor; - nextBox.h = basePixelCount * scaleFactor; - drawTargets.push(nextBox); - } - } else if (evt.button == 2) { - if (enableErasing && !paintMode) { - // right click, also gotta make sure mask blob isn't being used as it's visually inconsistent with behavior of erased region - ctx = imgCanvas.getContext('2d'); - if (snapToGrid) { - ctx.clearRect( - canvasX + - snap(canvasX) - - (basePixelCount * scaleFactor) / 2, - canvasY + - snap(canvasY) - - (basePixelCount * scaleFactor) / 2, - basePixelCount * scaleFactor, - basePixelCount * scaleFactor - ); - } else { - ctx.clearRect( - canvasX - (basePixelCount * scaleFactor) / 2, - canvasY - (basePixelCount * scaleFactor) / 2, - basePixelCount * scaleFactor, - basePixelCount * scaleFactor - ); - } - } - } + const rect = ovCanvas.getBoundingClientRect(); + var oddOffset = 0; + if (scaleFactor % 2 != 0) { + oddOffset = basePixelCount / 2; + } + if (evt.button == 0) { + // left click + if (placingArbitraryImage) { + var nextBox = {}; + nextBox.x = evt.offsetX; + nextBox.y = evt.offsetY; + nextBox.w = arbitraryImageData.width; + nextBox.h = arbitraryImageData.height; + dropTargets.push(nextBox); + } else if (!paintMode) { + //const rect = ovCanvas.getBoundingClientRect() + var nextBox = {}; + nextBox.x = + evt.clientX - + (basePixelCount * scaleFactor) / 2 - + rect.left + + oddOffset; //origin is middle of the frame + nextBox.y = + evt.clientY - (basePixelCount * scaleFactor) / 2 - rect.top + oddOffset; //TODO make a way to set the origin to numpad dirs? + nextBox.w = basePixelCount * scaleFactor; + nextBox.h = basePixelCount * scaleFactor; + drawTargets.push(nextBox); + } + } else if (evt.button == 2) { + if (enableErasing && !paintMode) { + // right click, also gotta make sure mask blob isn't being used as it's visually inconsistent with behavior of erased region + ctx = imgCanvas.getContext("2d"); + if (snapToGrid) { + ctx.clearRect( + canvasX + snap(canvasX) - (basePixelCount * scaleFactor) / 2, + canvasY + snap(canvasY) - (basePixelCount * scaleFactor) / 2, + basePixelCount * scaleFactor, + basePixelCount * scaleFactor + ); + } else { + ctx.clearRect( + canvasX - (basePixelCount * scaleFactor) / 2, + canvasY - (basePixelCount * scaleFactor) / 2, + basePixelCount * scaleFactor, + basePixelCount * scaleFactor + ); + } + } + } } function mouseUp(evt) { - if (evt.button == 0) { - // left click - if (placingArbitraryImage) { - // jeez i REALLY need to refactor tons of this to not be duplicated all over, that's definitely my next chore after figuring out that razza frazza overmask fade - var target = dropTargets[dropTargets.length - 1]; //get the last one... why am i storing all of them? - snapOffsetX = 0; - snapOffsetY = 0; - if (snapToGrid) { - snapOffsetX = snap(target.x, false); - snapOffsetY = snap(target.y, false); - } - finalX = snapOffsetX + target.x; - finalY = snapOffsetY + target.y; + if (evt.button == 0) { + // left click + if (placingArbitraryImage) { + // jeez i REALLY need to refactor tons of this to not be duplicated all over, that's definitely my next chore after figuring out that razza frazza overmask fade + var target = dropTargets[dropTargets.length - 1]; //get the last one... why am i storing all of them? + snapOffsetX = 0; + snapOffsetY = 0; + if (snapToGrid) { + snapOffsetX = snap(target.x, false); + snapOffsetY = snap(target.y, false); + } + finalX = snapOffsetX + target.x; + finalY = snapOffsetY + target.y; - drawThis.x = finalX; - drawThis.y = finalY; - drawThis.w = target.w; - drawThis.h = target.h; - drawIt = drawThis; // i still think this is really stupid and redundant and unnecessary and redundant - drop(drawIt); - } else if (paintMode) { - clicked = false; - return; - } else { - if (!blockNewImages) { - //TODO seriously, refactor this - blockNewImages = true; - clearTargetMask(); - tgtCtx.strokeStyle = '#55000077'; - var drawIt = {}; //why am i doing this???? - var target = drawTargets[drawTargets.length - 1]; //get the last one... why am i storing all of them? - var oddOffset = 0; - if (scaleFactor % 2 != 0) { - oddOffset = basePixelCount / 2; - } - snapOffsetX = 0; - snapOffsetY = 0; - if (snapToGrid) { - snapOffsetX = snap(target.x); - snapOffsetY = snap(target.y); - } - finalX = snapOffsetX + target.x - oddOffset; - finalY = snapOffsetY + target.y - oddOffset; + drawThis.x = finalX; + drawThis.y = finalY; + drawThis.w = target.w; + drawThis.h = target.h; + drawIt = drawThis; // i still think this is really stupid and redundant and unnecessary and redundant + drop(drawIt); + } else if (paintMode) { + clicked = false; + return; + } else { + if (!blockNewImages) { + //TODO seriously, refactor this + blockNewImages = true; + marching = true; + var drawIt = {}; //why am i doing this???? + var target = drawTargets[drawTargets.length - 1]; //get the last one... why am i storing all of them? + var oddOffset = 0; + if (scaleFactor % 2 != 0) { + oddOffset = basePixelCount / 2; + } + snapOffsetX = 0; + snapOffsetY = 0; + if (snapToGrid) { + snapOffsetX = snap(target.x); + snapOffsetY = snap(target.y); + } + finalX = snapOffsetX + target.x - oddOffset; + finalY = snapOffsetY + target.y - oddOffset; - drawThis.x = finalX; - drawThis.y = finalY; - drawThis.w = target.w; - drawThis.h = target.h; - tgtCtx.strokeRect(finalX, finalY, target.w, target.h); - drawIt = drawThis; //TODO this is WRONG but also explicitly only draws the last image ... i think - //check if there's image data already there - // console.log(downX + ":" + downY + " :: " + this.isCanvasBlank(downX, downY)); - if ( - !isCanvasBlank( - drawIt.x, - drawIt.y, - drawIt.w, - drawIt.h, - imgCanvas - ) - ) { - // image exists, set up for img2img - var mainCanvasCtx = document - .getElementById('canvas') - .getContext('2d'); - const imgChunk = mainCanvasCtx.getImageData( - drawIt.x, - drawIt.y, - drawIt.w, - drawIt.h - ); // imagedata object of the image being outpainted - const imgChunkData = imgChunk.data; // imagedata.data object, a big inconvenient uint8clampedarray - // these are the 3 mask monitors on the bottom of the page - var initImgCanvas = document.getElementById( - 'initImgCanvasMonitor' - ); - var overMaskCanvas = document.getElementById( - 'overMaskCanvasMonitor' - ); - overMaskCanvas.width = initImgCanvas.width = target.w; //maskCanvas.width = target.w; - overMaskCanvas.height = initImgCanvas.height = target.h; //maskCanvas.height = target.h; - var initImgCanvasCtx = initImgCanvas.getContext('2d'); - var overMaskCanvasCtx = overMaskCanvas.getContext('2d'); - // get blank pixels to use as mask - const initImgData = mainCanvasCtx.createImageData( - drawIt.w, - drawIt.h - ); - const overMaskImgData = overMaskCanvasCtx.createImageData( - drawIt.w, - drawIt.h - ); - // cover entire masks in black before adding masked areas + drawThis.x = marchCoords.x = finalX; + drawThis.y = marchCoords.y = finalY; + drawThis.w = marchCoords.w = target.w; + drawThis.h = marchCoords.h = target.h; + march(finalX, finalY, target.w, target.h); + drawIt = drawThis; //TODO this is WRONG but also explicitly only draws the last image ... i think + //check if there's image data already there + // console.log(downX + ":" + downY + " :: " + this.isCanvasBlank(downX, downY)); + if (!isCanvasBlank(drawIt.x, drawIt.y, drawIt.w, drawIt.h, imgCanvas)) { + // image exists, set up for img2img + var mainCanvasCtx = document + .getElementById("canvas") + .getContext("2d"); + const imgChunk = mainCanvasCtx.getImageData( + drawIt.x, + drawIt.y, + drawIt.w, + drawIt.h + ); // imagedata object of the image being outpainted + const imgChunkData = imgChunk.data; // imagedata.data object, a big inconvenient uint8clampedarray + // these are the 3 mask monitors on the bottom of the page + var initImgCanvas = document.getElementById("initImgCanvasMonitor"); + var overMaskCanvas = document.getElementById("overMaskCanvasMonitor"); + overMaskCanvas.width = initImgCanvas.width = target.w; //maskCanvas.width = target.w; + overMaskCanvas.height = initImgCanvas.height = target.h; //maskCanvas.height = target.h; + var initImgCanvasCtx = initImgCanvas.getContext("2d"); + var overMaskCanvasCtx = overMaskCanvas.getContext("2d"); + // get blank pixels to use as mask + const initImgData = mainCanvasCtx.createImageData(drawIt.w, drawIt.h); + let overMaskImgData = overMaskCanvasCtx.createImageData( + drawIt.w, + drawIt.h + ); + // cover entire masks in black before adding masked areas - for (let i = 0; i < imgChunkData.length; i += 4) { - // l->r, top->bottom, R G B A pixel values in a big ol array - // make a simple mask - if (imgChunkData[i + 3] == 0) { - // rgba pixel values, 4th one is alpha, if it's 0 there's "nothing there" in the image display canvas and its time to outpaint - overMaskImgData.data[i] = 255; // white mask gets painted over - overMaskImgData.data[i + 1] = 255; - overMaskImgData.data[i + 2] = 255; - overMaskImgData.data[i + 3] = 255; + for (let i = 0; i < imgChunkData.length; i += 4) { + // l->r, top->bottom, R G B A pixel values in a big ol array + // make a simple mask + if (imgChunkData[i + 3] == 0) { + // rgba pixel values, 4th one is alpha, if it's 0 there's "nothing there" in the image display canvas and its time to outpaint + overMaskImgData.data[i] = 255; // white mask gets painted over + overMaskImgData.data[i + 1] = 255; + overMaskImgData.data[i + 2] = 255; + overMaskImgData.data[i + 3] = 255; - initImgData.data[i] = 0; // null area on initial image becomes opaque black pixels - initImgData.data[i + 1] = 0; - initImgData.data[i + 2] = 0; - initImgData.data[i + 3] = 255; - } else { - // leave these pixels alone - overMaskImgData.data[i] = 0; // black mask gets ignored for in/outpainting - overMaskImgData.data[i + 1] = 0; - overMaskImgData.data[i + 2] = 0; - overMaskImgData.data[i + 3] = 255; // but it still needs an opaque alpha channel + initImgData.data[i] = 0; // null area on initial image becomes opaque black pixels + initImgData.data[i + 1] = 0; + initImgData.data[i + 2] = 0; + initImgData.data[i + 3] = 255; + } else { + // leave these pixels alone + overMaskImgData.data[i] = 0; // black mask gets ignored for in/outpainting + overMaskImgData.data[i + 1] = 0; + overMaskImgData.data[i + 2] = 0; + overMaskImgData.data[i + 3] = 255; // but it still needs an opaque alpha channel - initImgData.data[i] = imgChunkData[i]; // put the original picture back in the painted area - initImgData.data[i + 1] = imgChunkData[i + 1]; - initImgData.data[i + 2] = imgChunkData[i + 2]; - initImgData.data[i + 3] = imgChunkData[i + 3]; //it's still RGBA so we can handily do this in nice chunks'o'4 - } - } - // make a list of all the white pixels to expand so we don't waste time on non-mask pixels - let pix = { x: [], y: [], index: [] }; - var x, y, index; - for (y = 0; y < drawIt.h; y++) { - for (x = 0; x < drawIt.w; x++) { - index = (y * drawIt.w + x) * 4; - if (overMaskImgData.data[index] > 0) { - pix.x.push(x); - pix.y.push(y); - pix.index.push(index); - } - } - } - for (i = 0; i < pix.index.length; i++) { - // get the index in the stupid array - // why? it's unused - // var currentMaskPixelIndex = pix.index[i]; + initImgData.data[i] = imgChunkData[i]; // put the original picture back in the painted area + initImgData.data[i + 1] = imgChunkData[i + 1]; + initImgData.data[i + 2] = imgChunkData[i + 2]; + initImgData.data[i + 3] = imgChunkData[i + 3]; //it's still RGBA so we can handily do this in nice chunks'o'4 + } + } + if (overMaskPx > 0) { + // https://stackoverflow.com/a/30204783 ???? !!!!!!!! + overMaskCanvasCtx.fillStyle = "black"; + overMaskCanvasCtx.fillRect(0, 0, drawIt.w, drawIt.h); // fill with black instead of null to start + for (i = 0; i < overMaskImgData.data.length; i += 4) { + if (overMaskImgData.data[i] == 255) { + // white pixel? + // just blotch all over the thing + var rando = Math.floor(Math.random() * overMaskPx); + overMaskCanvasCtx.beginPath(); + overMaskCanvasCtx.arc( + (i / 4) % overMaskCanvas.width, + Math.floor(i / 4 / overMaskCanvas.width), + scaleFactor + rando, // was 4 * sf + rando, too big + 0, + 2 * Math.PI, + true + ); + overMaskCanvasCtx.fillStyle = "#FFFFFFFF"; + overMaskCanvasCtx.fill(); + } + } + overMaskImgData = overMaskCanvasCtx.getImageData( + 0, + 0, + overMaskCanvas.width, + overMaskCanvas.height + ); + overMaskCanvasCtx.putImageData(overMaskImgData, 0, 0); + } + // also check for painted masks in region, add them as white pixels to mask canvas + const maskChunk = maskPaintCtx.getImageData( + drawIt.x, + drawIt.y, + drawIt.w, + drawIt.h + ); + const maskChunkData = maskChunk.data; + for (let i = 0; i < maskChunkData.length; i += 4) { + if (maskChunkData[i + 3] != 0) { + overMaskImgData.data[i] = 255; + overMaskImgData.data[i + 1] = 255; + overMaskImgData.data[i + 2] = 255; + overMaskImgData.data[i + 3] = 255; + } + } + // backup any painted masks ingested then them, replacable if user doesn't like resultant image + var clearArea = maskPaintCtx.createImageData(drawIt.w, drawIt.h); + backupMaskChunk = maskChunk; + backupMaskX = drawIt.x; + backupMaskY = drawIt.y; - // for any horizontal expansion, we need to ensure that the target pixel is in the same Y row - // horizontal left (west) is index-4 per pixel - // horizontal right (east) is index+4 per pixel - var currentMaskPixelY = pix.y[i]; - - // for any vertical expansion, we need to ensure that the target pixel is in the same X column - // vertical up (north) is index-(imagedata.width) per pixel - // vertical down (south) is index+(imagedata.width) per pixel - var currentMaskPixelX = pix.x[i]; - - // i hate uint8clampedarray and math - // primarily math - // actually just my brain - // ok so now lets check neighbors to see if they're in the same row/column - for (j = overMaskPx; j > 0; j--) { - // set a variable to the extreme end of the overmask size and work our way back inwards - // i hate uint8clampedarray and math - // this is so inefficient but i warned you all i'm bad at this - //TODO refactor like all of this, it's horrible and shameful - // BUT IT WORKS - // but it is crushingly inefficient i'm sure - // BUT IT WORKS and i came up with it all by myself because i'm a big boy - - // ... sigh it doesn't work _well_ - // just moves the seam - //TODO find a way to fade/gradient the edge without making weird artifacts or literally crashing the browser with inefficient data storage - - // west - var potentialPixelIndex = - (currentMaskPixelY * drawIt.w + - currentMaskPixelX) * - 4 - - j * 4; - var potentialPixelX = - (potentialPixelIndex / 4) % drawIt.w; - var potentialPixelY = Math.floor( - potentialPixelIndex / 4 / drawIt.w - ); - // west/east: ENSURE SAME ROW using the y axis unintuitively - if (potentialPixelY == currentMaskPixelY) { - // ok then - // ensure it's not already a mask pixel - if ( - overMaskImgData.data[potentialPixelIndex] != - 255 - ) { - // welp fingers crossed - overMaskImgData.data[ - potentialPixelIndex - ] = 255; - overMaskImgData.data[ - potentialPixelIndex + 1 - ] = 255; - overMaskImgData.data[ - potentialPixelIndex + 2 - ] = 255; - overMaskImgData.data[ - potentialPixelIndex + 3 - ] = 255; - } - } - - // east - var potentialPixelIndex = - (currentMaskPixelY * drawIt.w + - currentMaskPixelX) * - 4 + - j * 4; - var potentialPixelX = - (potentialPixelIndex / 4) % drawIt.w; - var potentialPixelY = Math.floor( - potentialPixelIndex / 4 / drawIt.w - ); - if (potentialPixelY == currentMaskPixelY) { - if ( - overMaskImgData.data[potentialPixelIndex] != - 255 - ) { - overMaskImgData.data[ - potentialPixelIndex - ] = 255; - overMaskImgData.data[ - potentialPixelIndex + 1 - ] = 255; - overMaskImgData.data[ - potentialPixelIndex + 2 - ] = 255; - overMaskImgData.data[ - potentialPixelIndex + 3 - ] = 255; - } - } - - // north - var potentialPixelIndex = - (currentMaskPixelY * drawIt.w + - currentMaskPixelX) * - 4 - - j * drawIt.w * 4; - var potentialPixelX = - (potentialPixelIndex / 4) % drawIt.w; - var potentialPixelY = Math.floor( - potentialPixelIndex / 4 / drawIt.w - ); - // north/south: ENSURE SAME COLUMN using the x axis unintuitively - if (potentialPixelX == currentMaskPixelX) { - if ( - overMaskImgData.data[potentialPixelIndex] != - 255 - ) { - overMaskImgData.data[ - potentialPixelIndex - ] = 255; - overMaskImgData.data[ - potentialPixelIndex + 1 - ] = 255; - overMaskImgData.data[ - potentialPixelIndex + 2 - ] = 255; - overMaskImgData.data[ - potentialPixelIndex + 3 - ] = 255; - } - } - - // south - var potentialPixelIndex = - (currentMaskPixelY * drawIt.w + - currentMaskPixelX) * - 4 + - j * drawIt.w * 4; - var potentialPixelX = - (potentialPixelIndex / 4) % drawIt.w; - var potentialPixelY = Math.floor( - potentialPixelIndex / 4 / drawIt.w - ); - if (potentialPixelX == currentMaskPixelX) { - if ( - overMaskImgData.data[potentialPixelIndex] != - 255 - ) { - overMaskImgData.data[ - potentialPixelIndex - ] = 255; - overMaskImgData.data[ - potentialPixelIndex + 1 - ] = 255; - overMaskImgData.data[ - potentialPixelIndex + 2 - ] = 255; - overMaskImgData.data[ - potentialPixelIndex + 3 - ] = 255; - } - } - } - } - - // also check for painted masks in region, add them as white pixels to mask canvas - const maskChunk = maskPaintCtx.getImageData( - drawIt.x, - drawIt.y, - drawIt.w, - drawIt.h - ); - const maskChunkData = maskChunk.data; - for (let i = 0; i < maskChunkData.length; i += 4) { - if (maskChunkData[i + 3] != 0) { - overMaskImgData.data[i] = 255; - overMaskImgData.data[i + 1] = 255; - overMaskImgData.data[i + 2] = 255; - overMaskImgData.data[i + 3] = 255; - } - } - // backup any painted masks ingested then them, replacable if user doesn't like resultant image - var clearArea = maskPaintCtx.createImageData( - drawIt.w, - drawIt.h - ); - backupMaskChunk = maskChunk; - backupMaskX = drawIt.x; - backupMaskY = drawIt.y; - - var clearD = clearArea.data; - for (let i = 0; i < clearD.length; i++) { - clearD[i] = 0; // just null it all out - } - maskPaintCtx.putImageData(clearArea, drawIt.x, drawIt.y); - // mask monitors - overMaskCanvasCtx.putImageData(overMaskImgData, 0, 0); // :pray: - var overMaskBase64 = overMaskCanvas.toDataURL(); - initImgCanvasCtx.putImageData(initImgData, 0, 0); - var initImgBase64 = initImgCanvas.toDataURL(); - // anyway all that to say NOW let's run img2img - endpoint = 'img2img'; - stableDiffusionData.mask = overMaskBase64; - stableDiffusionData.init_images = [initImgBase64]; - // slightly more involved than txt2img - } else { - // time to run txt2img - endpoint = 'txt2img'; - // easy enough - } - stableDiffusionData.prompt = - document.getElementById('prompt').value; - stableDiffusionData.negative_prompt = - document.getElementById('negPrompt').value; - stableDiffusionData.width = drawIt.w; - stableDiffusionData.height = drawIt.h; - stableDiffusionData.firstphase_height = drawIt.h / 2; - stableDiffusionData.firstphase_width = drawIt.w / 2; - dream(drawIt.x, drawIt.y, stableDiffusionData); - } - } - } + var clearD = clearArea.data; + for (let i = 0; i < clearD.length; i++) { + clearD[i] = 0; // just null it all out + } + maskPaintCtx.putImageData(clearArea, drawIt.x, drawIt.y); + // mask monitors + overMaskCanvasCtx.putImageData(overMaskImgData, 0, 0); // :pray: + var overMaskBase64 = overMaskCanvas.toDataURL(); + initImgCanvasCtx.putImageData(initImgData, 0, 0); + var initImgBase64 = initImgCanvas.toDataURL(); + // anyway all that to say NOW let's run img2img + endpoint = "img2img"; + stableDiffusionData.mask = overMaskBase64; + stableDiffusionData.init_images = [initImgBase64]; + // slightly more involved than txt2img + } else { + // time to run txt2img + endpoint = "txt2img"; + // easy enough + } + stableDiffusionData.prompt = document.getElementById("prompt").value; + stableDiffusionData.negative_prompt = + document.getElementById("negPrompt").value; + stableDiffusionData.width = drawIt.w; + stableDiffusionData.height = drawIt.h; + stableDiffusionData.firstphase_height = drawIt.h / 2; + stableDiffusionData.firstphase_width = drawIt.w / 2; + dream(drawIt.x, drawIt.y, stableDiffusionData); + } + } + } } -const changeScaleFactor = sliderChangeHandlerFactory( - 'scaleFactor', - 'scaleFactorTxt', - 'scaleFactor', - 8, - (k, v) => (scaleFactor = v), - (k) => scaleFactor -); -const changeSteps = sliderChangeHandlerFactory( - 'steps', - 'stepsTxt', - 'steps', - 30 -); - function changePaintMode() { - paintMode = document.getElementById('cbxPaint').checked; - clearTargetMask(); - ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); + paintMode = document.getElementById("cbxPaint").checked; + clearTargetMask(); + ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); } function changeEnableErasing() { - // yeah because this is for the image layer - enableErasing = document.getElementById('cbxEnableErasing').checked; - localStorage.setItem('enable_erase', enableErasing); + // yeah because this is for the image layer + enableErasing = document.getElementById("cbxEnableErasing").checked; + localStorage.setItem("enable_erase", enableErasing); } function changeSampler() { - stableDiffusionData.sampler_index = - document.getElementById('samplerSelect').value; - localStorage.setItem('sampler', stableDiffusionData.sampler_index); + stableDiffusionData.sampler_index = + document.getElementById("samplerSelect").value; + localStorage.setItem("sampler", stableDiffusionData.sampler_index); } const changeCfgScale = sliderChangeHandlerFactory( - 'cfgScale', - 'cfgScaleTxt', - 'cfg_scale', - 7.0 + "cfgScale", + "cfgScaleTxt", + "cfg_scale", + 7.0 ); const changeBatchSize = sliderChangeHandlerFactory( - 'batchSize', - 'batchSizeText', - 'batch_size', - 2 + "batchSize", + "batchSizeText", + "batch_size", + 2 ); const changeBatchCount = sliderChangeHandlerFactory( - 'batchCount', - 'batchCountText', - 'n_iter', - 2 + "batchCount", + "batchCountText", + "n_iter", + 2 +); +const changeScaleFactor = sliderChangeHandlerFactory( + "scaleFactor", + "scaleFactorTxt", + "scaleFactor", + 8, + (k, v) => (scaleFactor = v), + (k) => scaleFactor +); +const changeSteps = sliderChangeHandlerFactory( + "steps", + "stepsTxt", + "steps", + 30 ); function changeSnapMode() { - snapToGrid = document.getElementById('cbxSnap').checked; + snapToGrid = document.getElementById("cbxSnap").checked; } function changeMaskBlur() { - stableDiffusionData.mask_blur = document.getElementById('maskBlur').value; - localStorage.setItem('mask_blur', stableDiffusionData.mask_blur); + stableDiffusionData.mask_blur = document.getElementById("maskBlur").value; + localStorage.setItem("mask_blur", stableDiffusionData.mask_blur); } function changeSeed() { - stableDiffusionData.seed = document.getElementById('seed').value; - localStorage.setItem('seed', stableDiffusionData.seed); + stableDiffusionData.seed = document.getElementById("seed").value; + localStorage.setItem("seed", stableDiffusionData.seed); } function changeOverMaskPx() { - overMaskPx = document.getElementById('overMaskPx').value; - localStorage.setItem('overmask_px', overMaskPx); + overMaskPx = document.getElementById("overMaskPx").value; + localStorage.setItem("overmask_px", overMaskPx); } function changeHiResFix() { - stableDiffusionData.enable_hr = Boolean( - document.getElementById('cbxHRFix').checked - ); - localStorage.setItem('enable_hr', stableDiffusionData.enable_hr); + stableDiffusionData.enable_hr = Boolean( + document.getElementById("cbxHRFix").checked + ); + localStorage.setItem("enable_hr", stableDiffusionData.enable_hr); } function isCanvasBlank(x, y, w, h, specifiedCanvas) { - var canvas = document.getElementById(specifiedCanvas.id); - return !canvas - .getContext('2d') - .getImageData(x, y, w, h) - .data.some((channel) => channel !== 0); + var canvas = document.getElementById(specifiedCanvas.id); + return !canvas + .getContext("2d") + .getImageData(x, y, w, h) + .data.some((channel) => channel !== 0); } function drawBackground() { - bgCtx.lineWidth = 1; - bgCtx.strokeStyle = '#999'; - var gridbox = bgCanvas.getBoundingClientRect(); - for (var i = 0; i < gridbox.width; i += 64) { - bgCtx.moveTo(i, 0); - bgCtx.lineTo(i, bgCanvas.height); - bgCtx.stroke(); - } - for (var i = 0; i < gridbox.height; i += 64) { - bgCtx.moveTo(0, i); - bgCtx.lineTo(gridbox.width, i); - bgCtx.stroke(); - } + bgCtx.lineWidth = 1; + bgCtx.strokeStyle = "#999"; + var gridbox = bgCanvas.getBoundingClientRect(); + for (var i = 0; i < gridbox.width; i += 64) { + bgCtx.moveTo(i, 0); + bgCtx.lineTo(i, bgCanvas.height); + bgCtx.stroke(); + } + for (var i = 0; i < gridbox.height; i += 64) { + bgCtx.moveTo(0, i); + bgCtx.lineTo(gridbox.width, i); + bgCtx.stroke(); + } } function preloadImage() { - // gonna legit scream - document.getElementById('overlayCanvas').onmousemove = null; - document.getElementById('overlayCanvas').onmousedown = null; - document.getElementById('overlayCanvas').onmouseup = null; + // possible firefox-only bug? + // attempt to prevent requesting a dream if double-clicking a selected image + document.getElementById("overlayCanvas").onmousemove = null; + document.getElementById("overlayCanvas").onmousedown = null; + document.getElementById("overlayCanvas").onmouseup = null; - var file = document.getElementById('preloadImage').files[0]; - var reader = new FileReader(); - reader.onload = function (evt) { - var imgCanvas = document.createElement('canvas'); - var imgCtx = imgCanvas.getContext('2d'); - arbitraryImage = new Image(); - arbitraryImage.onload = function () { - blockNewImages = true; - // now put it into imagedata for canvas fun - imgCanvas.width = arbitraryImage.width; - imgCanvas.height = arbitraryImage.height; - imgCtx.drawImage(arbitraryImage, 0, 0); - arbitraryImageData = imgCtx.getImageData( - 0, - 0, - arbitraryImage.width, - arbitraryImage.height - ); // can't use that to drawImage on a canvas... - arbitraryImageBitmap = createImageBitmap(arbitraryImageData); // apparently that either... maybe just the raw image? - arbitraryImageBase64 = imgCanvas.toDataURL(); - placingArbitraryImage = true; - document.getElementById('overlayCanvas').onmousemove = mouseMove; - document.getElementById('overlayCanvas').onmousedown = mouseDown; - document.getElementById('overlayCanvas').onmouseup = mouseUp; - }; - arbitraryImage.src = evt.target.result; - }; - reader.readAsDataURL(file); + var file = document.getElementById("preloadImage").files[0]; + var reader = new FileReader(); + reader.onload = function (evt) { + var imgCanvas = document.createElement("canvas"); + var imgCtx = imgCanvas.getContext("2d"); + arbitraryImage = new Image(); + arbitraryImage.onload = function () { + blockNewImages = true; + // now put it into imagedata for canvas fun + imgCanvas.width = arbitraryImage.width; + imgCanvas.height = arbitraryImage.height; + imgCtx.drawImage(arbitraryImage, 0, 0); + arbitraryImageData = imgCtx.getImageData( + 0, + 0, + arbitraryImage.width, + arbitraryImage.height + ); // can't use that to drawImage on a canvas... + arbitraryImageBitmap = createImageBitmap(arbitraryImageData); // apparently that either... maybe just the raw image? + arbitraryImageBase64 = imgCanvas.toDataURL(); + placingArbitraryImage = true; + document.getElementById("overlayCanvas").onmousemove = mouseMove; + document.getElementById("overlayCanvas").onmousedown = mouseDown; + document.getElementById("overlayCanvas").onmouseup = mouseUp; + }; + arbitraryImage.src = evt.target.result; + }; + reader.readAsDataURL(file); } function downloadCanvas() { - var link = document.createElement('a'); - link.download = - new Date() - .toISOString() - .slice(0, 19) - .replace('T', ' ') - .replace(':', ' ') + ' openOutpaint image.png'; - var croppedCanvas = cropCanvas(imgCanvas); - if (croppedCanvas != null) { - link.href = croppedCanvas.toDataURL('image/png'); - link.click(); - } + var link = document.createElement("a"); + link.download = + new Date().toISOString().slice(0, 19).replace("T", " ").replace(":", " ") + + " openOutpaint image.png"; + var croppedCanvas = cropCanvas(imgCanvas); + if (croppedCanvas != null) { + link.href = croppedCanvas.toDataURL("image/png"); + link.click(); + } } function cropCanvas(sourceCanvas) { - var w = sourceCanvas.width; - var h = sourceCanvas.height; - var pix = { x: [], y: [] }; - var imageData = sourceCanvas.getContext('2d').getImageData(0, 0, w, h); - var x, y, index; + var w = sourceCanvas.width; + var h = sourceCanvas.height; + var pix = {x: [], y: []}; + var imageData = sourceCanvas.getContext("2d").getImageData(0, 0, w, h); + var x, y, index; - for (y = 0; y < h; y++) { - for (x = 0; x < w; x++) { - // lol i need to learn what this part does - index = (y * w + x) * 4; // OHHH OK this is setting the imagedata.data uint8clampeddataarray index for the specified x/y coords - //this part i get, this is checking that 4th RGBA byte for opacity - if (imageData.data[index + 3] > 0) { - pix.x.push(x); - pix.y.push(y); - } - } - } - // ...need to learn what this part does too :badpokerface: - // is this just determining the boundaries of non-transparent pixel data? - pix.x.sort(function (a, b) { - return a - b; - }); - pix.y.sort(function (a, b) { - return a - b; - }); - var n = pix.x.length - 1; - w = pix.x[n] - pix.x[0]; - h = pix.y[n] - pix.y[0]; - // yup sure looks like it + for (y = 0; y < h; y++) { + for (x = 0; x < w; x++) { + // lol i need to learn what this part does + index = (y * w + x) * 4; // OHHH OK this is setting the imagedata.data uint8clampeddataarray index for the specified x/y coords + //this part i get, this is checking that 4th RGBA byte for opacity + if (imageData.data[index + 3] > 0) { + pix.x.push(x); + pix.y.push(y); + } + } + } + // ...need to learn what this part does too :badpokerface: + // is this just determining the boundaries of non-transparent pixel data? + pix.x.sort(function (a, b) { + return a - b; + }); + pix.y.sort(function (a, b) { + return a - b; + }); + var n = pix.x.length - 1; + w = pix.x[n] - pix.x[0]; + h = pix.y[n] - pix.y[0]; + // yup sure looks like it - try { - var cut = sourceCanvas - .getContext('2d') - .getImageData(pix.x[0], pix.y[0], w, h); - var cutCanvas = document.createElement('canvas'); - cutCanvas.width = w; - cutCanvas.height = h; - cutCanvas.getContext('2d').putImageData(cut, 0, 0); - } catch (ex) { - // probably empty image - //TODO confirm edge cases? - cutCanvas = null; - } - return cutCanvas; + try { + var cut = sourceCanvas + .getContext("2d") + .getImageData(pix.x[0], pix.y[0], w, h); + var cutCanvas = document.createElement("canvas"); + cutCanvas.width = w; + cutCanvas.height = h; + cutCanvas.getContext("2d").putImageData(cut, 0, 0); + } catch (ex) { + // probably empty image + //TODO confirm edge cases? + cutCanvas = null; + } + return cutCanvas; } function checkIfWebuiIsRunning() { - var url = document.getElementById('host').value + '/startup-events'; - fetch(url) - .then((response) => { - if (response.status == 200) { - console.log('webui is running'); - } - }) - .catch((error) => { - alert( - 'WebUI doesnt seem to be running, please start it and try again\nCheck console for additional info\n' + - error - ); - }); + var url = document.getElementById("host").value + "/startup-events"; + fetch(url) + .then((response) => { + if (response.status == 200) { + console.log("webui is running"); + } + }) + .catch((error) => { + alert( + "WebUI doesnt seem to be running, please start it and try again\nCheck console for additional info\n" + + error + ); + }); } function loadSettings() { - // set default values if not set - var _sampler = - localStorage.getItem('sampler') == null - ? 'DDIM' - : localStorage.getItem('sampler'); - var _mask_blur = - localStorage.getItem('mask_blur') == null - ? 0 - : localStorage.getItem('mask_blur'); - var _seed = - localStorage.getItem('seed') == null - ? -1 - : localStorage.getItem('seed'); - var _enable_hr = Boolean( - localStorage.getItem('enable_hr') == (null || 'false') - ? false - : localStorage.getItem('enable_hr') - ); - var _enable_erase = Boolean( - localStorage.getItem('enable_erase') == (null || 'false') - ? false - : localStorage.getItem('enable_erase') - ); - var _overmask_px = - localStorage.getItem('overmask_px') == null - ? 0 - : localStorage.getItem('overmask_px'); + // set default values if not set + var _sampler = + localStorage.getItem("sampler") == null + ? "DDIM" + : localStorage.getItem("sampler"); + var _mask_blur = + localStorage.getItem("mask_blur") == null + ? 0 + : localStorage.getItem("mask_blur"); + var _seed = + localStorage.getItem("seed") == null ? -1 : localStorage.getItem("seed"); + var _enable_hr = Boolean( + localStorage.getItem("enable_hr") == (null || "false") + ? false + : localStorage.getItem("enable_hr") + ); + var _enable_erase = Boolean( + localStorage.getItem("enable_erase") == (null || "false") + ? false + : localStorage.getItem("enable_erase") + ); + var _overmask_px = + localStorage.getItem("overmask_px") == null + ? 0 + : localStorage.getItem("overmask_px"); - // set the values into the UI - document.getElementById('samplerSelect').value = String(_sampler); - document.getElementById('maskBlur').value = Number(_mask_blur); - document.getElementById('seed').value = Number(_seed); - document.getElementById('cbxHRFix').checked = Boolean(_enable_hr); - document.getElementById('cbxEnableErasing').checked = - Boolean(_enable_erase); - document.getElementById('overMaskPx').value = Number(_overmask_px); + // set the values into the UI + document.getElementById("samplerSelect").value = String(_sampler); + document.getElementById("maskBlur").value = Number(_mask_blur); + document.getElementById("seed").value = Number(_seed); + document.getElementById("cbxHRFix").checked = Boolean(_enable_hr); + document.getElementById("cbxEnableErasing").checked = Boolean(_enable_erase); + document.getElementById("overMaskPx").value = Number(_overmask_px); } diff --git a/js/settingsbar.js b/js/settingsbar.js index e50228e..84120a3 100644 --- a/js/settingsbar.js +++ b/js/settingsbar.js @@ -1,48 +1,48 @@ dragElement(document.getElementById("infoContainer")); function dragElement(elmnt) { - var p1 = 0, - p2 = 0, - p3 = 0, - p4 = 0; - var draggableElements = document.getElementsByClassName('draggable'); - for (var i = 0; i < draggableElements.length; i++) { - draggableElements[i].onmousedown = dragMouseDown; - } + var p1 = 0, + p2 = 0, + p3 = 0, + p4 = 0; + var draggableElements = document.getElementsByClassName("draggable"); + for (var i = 0; i < draggableElements.length; i++) { + draggableElements[i].onmousedown = dragMouseDown; + } - function dragMouseDown(e) { - e.preventDefault(); - p3 = e.clientX; - p4 = e.clientY; - document.onmouseup = closeDragElement; - document.onmousemove = elementDrag; - } + function dragMouseDown(e) { + e.preventDefault(); + p3 = e.clientX; + p4 = e.clientY; + document.onmouseup = closeDragElement; + document.onmousemove = elementDrag; + } - function elementDrag(e) { - e.preventDefault(); - p1 = p3 - e.clientX; - p2 = p4 - e.clientY; - elmnt.style.top = (elmnt.offsetTop - (p4-e.clientY)) + "px"; - elmnt.style.left = (elmnt.offsetLeft - (p3-e.clientX)) + "px"; - p3 = e.clientX; - p4 = e.clientY; - } + function elementDrag(e) { + e.preventDefault(); + p1 = p3 - e.clientX; + p2 = p4 - e.clientY; + elmnt.style.top = elmnt.offsetTop - (p4 - e.clientY) + "px"; + elmnt.style.left = elmnt.offsetLeft - (p3 - e.clientX) + "px"; + p3 = e.clientX; + p4 = e.clientY; + } - function closeDragElement() { - document.onmouseup = null; - document.onmousemove = null; - } + function closeDragElement() { + document.onmouseup = null; + document.onmousemove = null; + } } var coll = document.getElementsByClassName("collapsible"); for (var i = 0; i < coll.length; i++) { - coll[i].addEventListener("click", function () { - this.classList.toggle("active"); - var content = this.nextElementSibling; - if (content.style.maxHeight) { - content.style.maxHeight = null; - } else { - content.style.maxHeight = content.scrollHeight + "px"; - } - }); -} \ No newline at end of file + coll[i].addEventListener("click", function () { + this.classList.toggle("active"); + var content = this.nextElementSibling; + if (content.style.maxHeight) { + content.style.maxHeight = null; + } else { + content.style.maxHeight = content.scrollHeight + "px"; + } + }); +}