From 3062766c13aa2a6f9a2f42b06a518486c5433c5e Mon Sep 17 00:00:00 2001 From: tim h Date: Sat, 19 Nov 2022 21:32:35 -0600 Subject: [PATCH 01/22] right-click erase is now an option --- index.html | 2 ++ js/index.js | 12 +++++++++++- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/index.html b/index.html index f4cc43d..4b2419b 100644 --- a/index.html +++ b/index.html @@ -65,6 +65,8 @@

+ +

diff --git a/js/index.js b/js/index.js index a051aab..a02c94a 100644 --- a/js/index.js +++ b/js/index.js @@ -117,6 +117,7 @@ var arbitraryImageData; 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 // info div, sometimes hidden let mouseXInfo = document.getElementById("mouseX"); @@ -464,7 +465,7 @@ function mouseDown(evt) { nextBox.h = basePixelCount * scaleFactor; drawTargets.push(nextBox); } - } else if (evt.button == 2 && !paintMode) { // right click, also gotta make sure mask blob isn't being used as it's visually inconsistent with behavior of erased region + } else if (evt.button == 2 && enableErasing && !paintMode) { // right click, also gotta make sure mask blob isn't being used as it's visually inconsistent with behavior of erased region // erase the canvas underneath the cursor, ctx = imgCanvas.getContext('2d'); if (snapToGrid) { @@ -753,11 +754,18 @@ function changePaintMode() { } function changeEraseMode() { + //TODO rename/refactor to make it more obvious this is just for painted masks eraseMode = document.getElementById("cbxErase").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); +} + function changeSampler() { stableDiffusionData.sampler_index = document.getElementById("samplerSelect").value; localStorage.setItem("sampler", stableDiffusionData.sampler_index); @@ -916,10 +924,12 @@ function loadSettings() { 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")); // 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); } From f8e87df6deb8116e83c2063a5996c66a9a23f357 Mon Sep 17 00:00:00 2001 From: tim h Date: Sat, 19 Nov 2022 21:39:18 -0600 Subject: [PATCH 02/22] =?UTF-8?q?=F0=9F=90=A0me=20update?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fb9db90..4100b47 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ this is a completely vanilla javascript and html canvas outpainting convenience - a big ol' 2560x1440 canvas for you to paint all over _(infinite canvas area planned, in //todo already)_ - inpainting/touchup blob - easily change samplers/steps/CFG/etc options for each dream summoned from the latent void - - right-click to erase output image under cursor + - optional right-click to erase output image under cursor - optional grid snapping for precision - optional overmasking for better seams between outpaints (suggested by [@lifeh2o](https://www.reddit.com/user/lifeh2o/overview) ([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)) and i think it's a slick idea) - optional hi-res fix for blank/txt2img dreams which, if enabled, uses image width/height / 2 as firstpass size From 2861c100bdd2a06469d4677ce05b43b634d205c5 Mon Sep 17 00:00:00 2001 From: tim h Date: Sat, 19 Nov 2022 22:10:20 -0600 Subject: [PATCH 03/22] font size/stroke visibility upgrade for us old folks --- css/index.css | 6 ++++++ js/index.js | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/css/index.css b/css/index.css index 04a5e95..4844a7d 100644 --- a/css/index.css +++ b/css/index.css @@ -175,4 +175,10 @@ button.tool:hover { .maskCanvasMonitor .overMaskCanvasMonitor .initImgCanvasMonitor { position: absolute; +} + +.strokeText { + -webkit-text-stroke: 1px #888; + font-size: 150%; + color: #000; } \ No newline at end of file diff --git a/js/index.js b/js/index.js index a02c94a..fcb9733 100644 --- a/js/index.js +++ b/js/index.js @@ -159,6 +159,7 @@ function startup() { changeOverMask(); changeOverMaskPx(); changeHiResFix(); + changeEnableErasing(); document.getElementById("overlayCanvas").onmousemove = mouseMove; document.getElementById("overlayCanvas").onmousedown = mouseDown; document.getElementById("overlayCanvas").onmouseup = mouseUp; @@ -230,7 +231,7 @@ function imageAcceptReject(x, y, data) { div.style.top = parseInt(y + data.parameters.height) + "px"; div.style.width = "150px"; div.style.height = "50px"; - div.innerHTML = " of " + div.innerHTML = " of " document.getElementById("tempDiv").appendChild(div); document.getElementById("currentImgIndex").innerText = "1"; document.getElementById("totalImgIndex").innerText = totalImagesReturned; From dc4e4a5752463592344449cfdcb37c65d3bba41a Mon Sep 17 00:00:00 2001 From: tim h Date: Sat, 19 Nov 2022 22:40:18 -0600 Subject: [PATCH 04/22] version bump and readfish --- README.md | 1 + index.html | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4100b47..9afb653 100644 --- a/README.md +++ b/README.md @@ -136,6 +136,7 @@ imported a transparent clip of a [relatively famous happy lil kitty](https://com - 0.0.5 - import arbitrary image from user's machine, "auto" txt2img hires fix, Very Important "new image" button [3b7f4e3](https://github.com/zero01101/openOutpaint/commit/3b7f4e3759d0d1f3b38eba7249e5b58bc8162c75) - 0.0.5.1 - erase rendered imagery with right click, ensure webUI is running (thanks again [@Kalekki](https://github.com/Kalekki)! (https://github.com/zero01101/openOutpaint/pull/7)) [54577d4](https://github.com/zero01101/openOutpaint/commit/54577d4f15fd7d014aaf2471e0042b3c48735e9c) - 0.0.5.5 - highly attractive and functional floating control panel which will be extremely useful for infinite canvas, [@Kalekki](https://github.com/Kalekki) is literally the best ([11](https://github.com/zero01101/openOutpaint/pull/11)) [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 (thanks [@seijihariki](https://github.com/seijihariki)!) [d9fb87a](https://github.com/zero01101/openOutpaint/commit/d9fb87acec6653f19a9dac7777bd866782303ebc) ## 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 4b2419b..b8a7422 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ - openOutpaint 🐠 0.0.5.5 + openOutpaint 🐠 0.0.5.6 From 24e8b9925908e865bdd0c2eedd30ba16673f7622 Mon Sep 17 00:00:00 2001 From: tim h Date: Sat, 19 Nov 2022 22:45:57 -0600 Subject: [PATCH 05/22] fishme --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9afb653..d4c5778 100644 --- a/README.md +++ b/README.md @@ -136,7 +136,7 @@ imported a transparent clip of a [relatively famous happy lil kitty](https://com - 0.0.5 - import arbitrary image from user's machine, "auto" txt2img hires fix, Very Important "new image" button [3b7f4e3](https://github.com/zero01101/openOutpaint/commit/3b7f4e3759d0d1f3b38eba7249e5b58bc8162c75) - 0.0.5.1 - erase rendered imagery with right click, ensure webUI is running (thanks again [@Kalekki](https://github.com/Kalekki)! (https://github.com/zero01101/openOutpaint/pull/7)) [54577d4](https://github.com/zero01101/openOutpaint/commit/54577d4f15fd7d014aaf2471e0042b3c48735e9c) - 0.0.5.5 - highly attractive and functional floating control panel which will be extremely useful for infinite canvas, [@Kalekki](https://github.com/Kalekki) is literally the best ([11](https://github.com/zero01101/openOutpaint/pull/11)) [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 (thanks [@seijihariki](https://github.com/seijihariki)!) [d9fb87a](https://github.com/zero01101/openOutpaint/commit/d9fb87acec6653f19a9dac7777bd866782303ebc) +- 0.0.5.6 - _FINALLY_ the sliders update their values in realtime, a nice overall start on cleaning up my mess (thanks [@seijihariki](https://github.com/seijihariki)!) ([14](https://github.com/zero01101/openOutpaint/pull/14)) [d9fb87a](https://github.com/zero01101/openOutpaint/commit/d9fb87acec6653f19a9dac7777bd866782303ebc) ## 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" From 62f20509e565bd710dde342ccaccc541adbe1063 Mon Sep 17 00:00:00 2001 From: tim h Date: Sat, 19 Nov 2022 22:59:42 -0600 Subject: [PATCH 06/22] define: vanity --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d4c5778..271a514 100644 --- a/README.md +++ b/README.md @@ -142,4 +142,4 @@ imported a transparent clip of a [relatively famous happy lil kitty](https://com 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" ![fullres fishy favicon](docs/05-openOutpaintFish.png) -the end 🐠 +~~the end~~ _𝒻𝒾𝓃_ 🐠 From 2ae516fa5bd3a90233e12f993a0bc4bbc0e150bc Mon Sep 17 00:00:00 2001 From: tim h Date: Sat, 19 Nov 2022 23:02:49 -0600 Subject: [PATCH 07/22] =?UTF-8?q?=E0=B2=A0=5F=E0=B2=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 271a514..11d6879 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,7 @@ imported a transparent clip of a [relatively famous happy lil kitty](https://com ## 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" + ![fullres fishy favicon](docs/05-openOutpaintFish.png) ~~the end~~ _𝒻𝒾𝓃_ 🐠 From 7824c7ee639570d64c2154d3bb403d77353a3dbf Mon Sep 17 00:00:00 2001 From: tim h Date: Sun, 20 Nov 2022 01:39:04 -0600 Subject: [PATCH 08/22] move royalty to their own section --- README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 11d6879..09398fc 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ 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 better seams between outpaints (suggested by [@lifeh2o](https://www.reddit.com/user/lifeh2o/overview) ([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)) and i think it's a slick idea) + - optional overmasking for potentially better seams between outpaints and it _sorta_ works currently but it needs fixing - 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 @@ -30,7 +30,7 @@ this is a completely vanilla javascript and html canvas outpainting convenience ### notes - technically you can run it directly in browser as a bare `file://` protocol webpage but that's _really_ not recommended as you'll have to add `null` as an accepted domain to your `--cors-allow-origins` option which just seems like it's a visibly poor decision - - a deliciously simple launch script (thanks [@jasonmhead](https://github.com/jasonmhead)! (https://github.com/zero01101/openOutpaint/pull/1)) is included to pop up a teensy tiny python-based local webserver, however you may have to manually `chmod +x openOutpaint.sh` on mac/linux + - a deliciously simple launch script is included to pop up a teensy tiny python-based local webserver, however you may have to manually `chmod +x openOutpaint.sh` on mac/linux - the address http://127.0.0.1:3456 will be used as the host address for openOutpaint in the below quickstart; your local setup may use a different IP address or port. you can of course modify the included launch script to point at a different port than 3456 if desired, as well - if your scale factor is > 8 (generating an image larger than 512x512), try the "auto txt2img HR fix" option @@ -75,14 +75,14 @@ this is a completely vanilla javascript and html canvas outpainting convenience - [ ] add error handling for async/XHR POST in case of, yknow, errors - [x] image erase region in case you decide later that you're not too happy with earlier results (technically i guess you could just mask over the entire region you dislike but that's... bad) - [ ] controls for the rest of API-available options (e.g. ~~hires fix~~, inpaint fill modes, etc) -- [x] ~~save user-set option values to browser localstorage to persist your preferred, uh, preferences~~ (thanks [@Kalekki](https://github.com/Kalekki)! (https://github.com/zero01101/openOutpaint/pull/5)) +- [x] ~~save user-set option values to browser localstorage to persist your preferred, uh, preferences~~ - [ ] render progress spinner/bar -- [x] ~~smart crop downloaded image~~ (thanks again [@Kalekki](https://github.com/Kalekki)! (https://github.com/zero01101/openOutpaint/pull/2)) +- [x] ~~smart crop downloaded image~~ - [x] import external image and ~~scale/~~ superimpose at will on canvas for in/outpainting - [ ] scaling of imported arbitrary image before superimposition - [ ] "numpad" selector for determining how reticle is anchored against actual mouse cursor (currently works like a "5" [center] on the "numpad" paradigm) - [ ] 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~~ (holy crap thanks again [@Kalekki](https://github.com/Kalekki)! (https://github.com/zero01101/openOutpaint/pull/11)) +- [x] ~~floating/togglable menu leftnav bar with categorized/sensibly laid-out options~~ - [ ] infinite canvas - [ ] global undo/redo - [ ] inpainting sketch tools @@ -138,6 +138,11 @@ 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, [@Kalekki](https://github.com/Kalekki) is literally the best ([11](https://github.com/zero01101/openOutpaint/pull/11)) [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 (thanks [@seijihariki](https://github.com/seijihariki)!) ([14](https://github.com/zero01101/openOutpaint/pull/14)) [d9fb87a](https://github.com/zero01101/openOutpaint/commit/d9fb87acec6653f19a9dac7777bd866782303ebc) +## 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), [delightful floating UI](https://github.com/zero01101/openOutpaint/pull/11) + - [@lifeh2o](https://www.reddit.com/user/lifeh2o/overview) ([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)) - overmasking concept that is still driving me crazy getting it to work right + ## 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" From b3a66dc2e00893882fb29c6116d3b94e2601a7a4 Mon Sep 17 00:00:00 2001 From: tim h Date: Sun, 20 Nov 2022 01:47:21 -0600 Subject: [PATCH 09/22] =?UTF-8?q?update=20=F0=9F=90=A0me?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 09398fc..db6fbfa 100644 --- a/README.md +++ b/README.md @@ -134,13 +134,14 @@ imported a transparent clip of a [relatively famous happy lil kitty](https://com - 0.0.4.2 - pull requests (<3), downloaded images now have a timestamped name, css breakout because hopefully this will become halfway attractive enough to benefit from non-inline stylesheets [70ad4fe](https://github.com/zero01101/openOutpaint/commit/70ad4fe081bdbd507afc5af3cc2a4435924b66e3) - 0.0.4.3 - overmasking, settings saved to localstorage [fca2e01](https://github.com/zero01101/openOutpaint/commit/fca2e01b8a4ecfe3d062c4090d5886e1033e8f38) - 0.0.5 - import arbitrary image from user's machine, "auto" txt2img hires fix, Very Important "new image" button [3b7f4e3](https://github.com/zero01101/openOutpaint/commit/3b7f4e3759d0d1f3b38eba7249e5b58bc8162c75) -- 0.0.5.1 - erase rendered imagery with right click, ensure webUI is running (thanks again [@Kalekki](https://github.com/Kalekki)! (https://github.com/zero01101/openOutpaint/pull/7)) [54577d4](https://github.com/zero01101/openOutpaint/commit/54577d4f15fd7d014aaf2471e0042b3c48735e9c) -- 0.0.5.5 - highly attractive and functional floating control panel which will be extremely useful for infinite canvas, [@Kalekki](https://github.com/Kalekki) is literally the best ([11](https://github.com/zero01101/openOutpaint/pull/11)) [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 (thanks [@seijihariki](https://github.com/seijihariki)!) ([14](https://github.com/zero01101/openOutpaint/pull/14)) [d9fb87a](https://github.com/zero01101/openOutpaint/commit/d9fb87acec6653f19a9dac7777bd866782303ebc) +- 0.0.5.1 - erase rendered imagery with right click, ensure webUI is running [54577d4](https://github.com/zero01101/openOutpaint/commit/54577d4f15fd7d014aaf2471e0042b3c48735e9c) +- 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) ## 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), [delightful floating UI](https://github.com/zero01101/openOutpaint/pull/11) + - [@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) + - [@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) ([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)) - overmasking concept that is still driving me crazy getting it to work right ## what's with the fish? From c4b1627386fe1c3c8fc5053ae73d41c3779be76f Mon Sep 17 00:00:00 2001 From: tim h Date: Sun, 20 Nov 2022 01:50:24 -0600 Subject: [PATCH 10/22] blblblblblblbl --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index db6fbfa..5265f7e 100644 --- a/README.md +++ b/README.md @@ -142,7 +142,7 @@ imported a transparent clip of a [relatively famous happy lil kitty](https://com - [@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) - [@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) ([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)) - overmasking concept that is still driving me crazy getting it to work right + - [@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)) ## 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" From aefd45f4366f974c2421221a6a199104777dc1cc Mon Sep 17 00:00:00 2001 From: tim h Date: Sun, 20 Nov 2022 11:38:24 -0600 Subject: [PATCH 11/22] removed redundant overmask toggle overmaskpx = 0 effectively disables it anyway sets overmaskpx to a localstorage option removes moot "plain" mask monitor --- README.md | 2 +- index.html | 10 ++++------ js/index.js | 53 ++++++++++++++--------------------------------------- 3 files changed, 19 insertions(+), 46 deletions(-) diff --git a/README.md b/README.md index 5265f7e..2031ee1 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@ 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 + - 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 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 diff --git a/index.html b/index.html index b8a7422..4351693 100644 --- a/index.html +++ b/index.html @@ -73,9 +73,7 @@

- -
- +
@@ -158,9 +156,9 @@
- -

lol ur browser sucks

-

+

lol ur browser sucks


diff --git a/js/index.js b/js/index.js index fcb9733..66b5d4f 100644 --- a/js/index.js +++ b/js/index.js @@ -107,9 +107,7 @@ var backupMaskChunk = null; var backupMaskX = null; var backupMaskY = null; var totalImagesReturned; -// var maskEdgePixels = {}; -var overMask = true; -var overMaskPx = 10; +var overMaskPx = 16; var drawTargets = []; // is this needed? i only draw the last one anyway... var dropTargets = []; // uhhh yeah similar to the above but for arbitrary dropped images var arbitraryImage; @@ -156,7 +154,6 @@ function startup() { changeSnapMode(); changeMaskBlur(); changeSeed(); - changeOverMask(); changeOverMaskPx(); changeHiResFix(); changeEnableErasing(); @@ -530,21 +527,18 @@ function mouseUp(evt) { //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)) { - // img2img + // 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 maskCanvas = document.getElementById("maskCanvasMonitor"); var initImgCanvas = document.getElementById("initImgCanvasMonitor"); var overMaskCanvas = document.getElementById("overMaskCanvasMonitor"); - overMaskCanvas.width = initImgCanvas.width = maskCanvas.width = target.w; - overMaskCanvas.height = initImgCanvas.height = maskCanvas.height = target.h; - var maskCanvasCtx = maskCanvas.getContext("2d"); + 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 maskImgData = maskCanvasCtx.createImageData(drawIt.w, drawIt.h); 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 @@ -553,12 +547,7 @@ function mouseUp(evt) { // 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 - maskImgData.data[i] = 255; // white mask gets painted over - maskImgData.data[i + 1] = 255; - maskImgData.data[i + 2] = 255; - maskImgData.data[i + 3] = 255; - - overMaskImgData.data[i] = 255; //lets just set this up now + overMaskImgData.data[i] = 255; // white mask gets painted over overMaskImgData.data[i + 1] = 255; overMaskImgData.data[i + 2] = 255; overMaskImgData.data[i + 3] = 255; @@ -569,15 +558,10 @@ function mouseUp(evt) { initImgData.data[i + 2] = 0; initImgData.data[i + 3] = 255; } else { // leave these pixels alone - maskImgData.data[i] = 0; // black mask gets ignored for in/outpainting - maskImgData.data[i + 1] = 0; - maskImgData.data[i + 2] = 0; - maskImgData.data[i + 3] = 255; // but it still needs an opaque alpha channel - - overMaskImgData.data[i] = 0; + 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; + 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]; @@ -693,10 +677,6 @@ function mouseUp(evt) { const maskChunkData = maskChunk.data; for (let i = 0; i < maskChunkData.length; i += 4) { if (maskChunkData[i + 3] != 0) { - maskImgData.data[i] = 255; - maskImgData.data[i + 1] = 255; - maskImgData.data[i + 2] = 255; - maskImgData.data[i + 3] = 255; overMaskImgData.data[i] = 255; overMaskImgData.data[i + 1] = 255; overMaskImgData.data[i + 2] = 255; @@ -715,21 +695,17 @@ function mouseUp(evt) { } maskPaintCtx.putImageData(clearArea, drawIt.x, drawIt.y); // mask monitors - maskCanvasCtx.putImageData(maskImgData, 0, 0); - var maskBase64 = maskCanvas.toDataURL(); overMaskCanvasCtx.putImageData(overMaskImgData, 0, 0); // :pray: var overMaskBase64 = overMaskCanvas.toDataURL(); initImgCanvasCtx.putImageData(initImgData, 0, 0); var initImgBase64 = initImgCanvas.toDataURL(); - // img2img + // anyway all that to say NOW let's run img2img endpoint = "img2img"; - var selectedMask = overMask ? overMaskBase64 : maskBase64; - stableDiffusionData.mask = selectedMask; - // stableDiffusionData.mask = maskBase64; + stableDiffusionData.mask = overMaskBase64; stableDiffusionData.init_images = [initImgBase64]; // slightly more involved than txt2img } else { - // txt2img + // time to run txt2img endpoint = "txt2img"; // easy enough } @@ -790,12 +766,9 @@ function changeSeed() { localStorage.setItem("seed", stableDiffusionData.seed); } -function changeOverMask() { - overMask = document.getElementById("cbxOverMask").checked; -} - function changeOverMaskPx() { overMaskPx = document.getElementById("overMaskPx").value; + localStorage.setItem("overmask_px", overMaskPx); } function changeHiResFix() { @@ -920,12 +893,13 @@ function checkIfWebuiIsRunning() { } function loadSettings() { - // set default values if not set DEFAULTS + // 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); @@ -933,4 +907,5 @@ function loadSettings() { 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); } From d563e8ba3a356db63a502390a35c073243c0f45d Mon Sep 17 00:00:00 2001 From: Kalekki Date: Sun, 20 Nov 2022 18:24:17 +0200 Subject: [PATCH 12/22] make draggable class for possible future re-use --- css/index.css | 3 +++ index.html | 2 +- js/settingsbar.js | 5 ++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/css/index.css b/css/index.css index 4844a7d..bd2f317 100644 --- a/css/index.css +++ b/css/index.css @@ -55,6 +55,9 @@ z-index: 999; } +#draggable{ + cursor:move +} #DraggableTitleBar { z-index: 999; diff --git a/index.html b/index.html index 4351693..2c7922f 100644 --- a/index.html +++ b/index.html @@ -10,7 +10,7 @@
-
openOutpaint 🐠
+
openOutpaint 🐠
diff --git a/js/settingsbar.js b/js/settingsbar.js index 9531643..e50228e 100644 --- a/js/settingsbar.js +++ b/js/settingsbar.js @@ -5,7 +5,10 @@ function dragElement(elmnt) { p2 = 0, p3 = 0, p4 = 0; - document.getElementById('DraggableTitleBar').onmousedown = dragMouseDown; + var draggableElements = document.getElementsByClassName('draggable'); + for (var i = 0; i < draggableElements.length; i++) { + draggableElements[i].onmousedown = dragMouseDown; + } function dragMouseDown(e) { e.preventDefault(); From ff9d9309563069111a44f593ba43a11982d0adbf Mon Sep 17 00:00:00 2001 From: Kalekki Date: Sun, 20 Nov 2022 14:26:33 +0200 Subject: [PATCH 13/22] Mask erasing --- index.html | 7 ++++++- js/index.js | 32 +++++++++++++++++++++++++------- 2 files changed, 31 insertions(+), 8 deletions(-) diff --git a/index.html b/index.html index 2c7922f..453388f 100644 --- a/index.html +++ b/index.html @@ -69,8 +69,10 @@

+
@@ -88,6 +90,7 @@
+
@@ -106,7 +109,9 @@
- +
+ +

diff --git a/js/index.js b/js/index.js index 66b5d4f..1b5432b 100644 --- a/js/index.js +++ b/js/index.js @@ -92,6 +92,7 @@ var mouseX = 0; var mouseY = 0; var canvasX = 0; var canvasY = 0; +var heldButton = 0; var snapX = 0; var snapY = 0; var drawThis = {}; @@ -124,6 +125,7 @@ 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 @@ -371,12 +373,14 @@ 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 @@ -428,6 +432,19 @@ function mouseMove(evt) { maskPaintCtx.lineJoin = maskPaintCtx.lineCap = 'round'; maskPaintCtx.stroke(); } + // Erase mask if right button is held + // no reason to have to tick a checkbox for this, more intuitive for both erases (mask and actual images) to just work on right click and inform the user about it + if (evt.buttons == 2) { + maskPaintCtx.globalCompositeOperation = 'destination-out'; + maskPaintCtx.beginPath(); + maskPaintCtx.strokeStyle = "#FFFFFFFF"; + maskPaintCtx.lineWidth = 8 * scaleFactor; + maskPaintCtx.moveTo(prevMouseX, prevMouseY); + maskPaintCtx.lineTo(mouseX, mouseY); + maskPaintCtx.lineJoin = maskPaintCtx.lineCap = 'round'; + maskPaintCtx.stroke(); + + } prevMouseX = mouseX; prevMouseY = mouseY; } @@ -463,13 +480,14 @@ function mouseDown(evt) { nextBox.h = basePixelCount * scaleFactor; drawTargets.push(nextBox); } - } else if (evt.button == 2 && enableErasing && !paintMode) { // right click, also gotta make sure mask blob isn't being used as it's visually inconsistent with behavior of erased region - // erase the canvas underneath the cursor, - 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); + } 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); + } } } } From eab96a448dd4f3040f8cf963beba9ffe661d6b01 Mon Sep 17 00:00:00 2001 From: Kalekki Date: Sun, 20 Nov 2022 15:28:48 +0200 Subject: [PATCH 14/22] fix --- index.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/index.html b/index.html index 453388f..22de98b 100644 --- a/index.html +++ b/index.html @@ -69,10 +69,10 @@

-
- --> +
From e88dc0acf961c5303d451d76d740b812d845a5e6 Mon Sep 17 00:00:00 2001 From: tim h Date: Sun, 20 Nov 2022 11:56:37 -0600 Subject: [PATCH 15/22] bump version, remove pointless checkbox --- README.md | 5 +++-- index.html | 11 ++++++----- js/index.js | 14 +++----------- 3 files changed, 12 insertions(+), 18 deletions(-) diff --git a/README.md b/README.md index 2031ee1..4069e6f 100644 --- a/README.md +++ b/README.md @@ -102,7 +102,7 @@ please do! kindly indicate your OS, browser, versions of both, any errors in dev ## known bugs :( - generated images display +1px on x/y during approve/reject state, doesn't affect output, just annoying - - erase mask is like entirely broken + - ~~erase mask is like entirely broken~~ - ~~odd-numbered scale factors don't snap correctly~~ - ~~arbitrary "pasted" images require clicking twice to place them and i _don't know why_ [(yes i do)](#terrible), just getting them to be arbitrarily placable was a giant pain because i'm not got the smarts~~ - selecting an aribtrary image by double-clicking it in the file picker can sometimes trigger a dream request that errors out if your file picker is "above" the canvas; i tried to alleviate that by temporarily removing the mouse(move/down/up) handlers for the canvas context on selection of a file, but i'm POSITIVE it's an improper solution and not quite sure if it's even fully effective @@ -137,10 +137,11 @@ imported a transparent clip of a [relatively famous happy lil kitty](https://com - 0.0.5.1 - erase rendered imagery with right click, ensure webUI is running [54577d4](https://github.com/zero01101/openOutpaint/commit/54577d4f15fd7d014aaf2471e0042b3c48735e9c) - 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) ## 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) + - [@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)) diff --git a/index.html b/index.html index 22de98b..bd0b74d 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,7 @@ - openOutpaint 🐠 0.0.5.6 + openOutpaint 🐠 @@ -70,8 +70,8 @@
- -
+
@@ -109,9 +109,10 @@
-
+
- +
+ Alpha release v0.0.5.7

diff --git a/js/index.js b/js/index.js index 1b5432b..8cf2559 100644 --- a/js/index.js +++ b/js/index.js @@ -147,7 +147,6 @@ function startup() { drawBackground(); changeScaleFactor(); changePaintMode(); - changeEraseMode(); changeSampler(); changeSteps(); changeCfgScale(); @@ -443,7 +442,7 @@ function mouseMove(evt) { maskPaintCtx.lineTo(mouseX, mouseY); maskPaintCtx.lineJoin = maskPaintCtx.lineCap = 'round'; maskPaintCtx.stroke(); - + } prevMouseX = mouseX; prevMouseY = mouseY; @@ -480,8 +479,8 @@ function mouseDown(evt) { 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 + } 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); @@ -748,13 +747,6 @@ function changePaintMode() { ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); } -function changeEraseMode() { - //TODO rename/refactor to make it more obvious this is just for painted masks - eraseMode = document.getElementById("cbxErase").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; From ea64c138c334474e6a38ace00c8938aa98a5d281 Mon Sep 17 00:00:00 2001 From: Victor Seiji Hariki Date: Sun, 20 Nov 2022 23:03:07 -0300 Subject: [PATCH 16/22] Add generic mouse input handler input.js is now responsible for processing mouse input and translating it to relevant events. This allows for less bloat on the main logic in index.js and easy implementation of new functionality Signed-off-by: Victor Seiji Hariki --- index.html | 2 + js/input.js | 284 ++++++++++++++++++++++++++++++++++++++++++++++++++++ js/util.js | 27 +++++ 3 files changed, 313 insertions(+) create mode 100644 js/input.js create mode 100644 js/util.js diff --git a/index.html b/index.html index bd0b74d..ed2ec20 100644 --- a/index.html +++ b/index.html @@ -176,6 +176,8 @@
+ + diff --git a/js/input.js b/js/input.js new file mode 100644 index 0000000..98b005e --- /dev/null +++ b/js/input.js @@ -0,0 +1,284 @@ +const inputConfig = { + clickRadius: 10, // Radius to be considered a click (pixels). If farther, turns into a drag + clickTiming: 500, // Timing window to be considered a click (ms). If longer, turns into a drag + dClickTiming: 500, // Timing window to be considered a double click (ms). +}; + +/** + * Mouse input processing + */ +// Base object generator functions +function _context_coords() { + return { + dragging: { + left: null, + middle: null, + right: null, + }, + + prev: { + x: 0, + y: 0, + }, + + pos: { + x: 0, + y: 0, + }, + }; +} +function _mouse_observers() { + return { + // Simple click handlers + onclick: new Observer(), + // Double click handlers (will still trigger simple click handler as well) + ondclick: new Observer(), + // Drag handler + ondragstart: new Observer(), + ondrag: new Observer(), + ondragend: new Observer(), + // Paint handler (like drag handler, but with no delay); will trigger during clicks too + onpaintstart: new Observer(), + onpaint: new Observer(), + onpaintend: new Observer(), + }; +} + +function _context_observers() { + return { + left: _mouse_observers(), + middle: _mouse_observers(), + right: _mouse_observers(), + }; +} + +const mouse = { + buttons: { + right: null, + left: null, + middle: null, + }, + + // Mouse Actions in Window Coordinates + window: _context_coords(), + + // Mouse Actions in Canvas Coordinates + canvas: _context_coords(), + + // Mouse Actions in World Coordinates + world: _context_coords(), + + listen: { + window: _context_observers(), + canvas: _context_observers(), + world: _context_observers(), + }, +}; + +function _mouse_state_snapshot() { + return { + buttons: window.structuredClone(mouse.buttons), + window: window.structuredClone(mouse.window), + canvas: window.structuredClone(mouse.canvas), + world: window.structuredClone(mouse.world), + }; +} + +const _double_click_timeout = {}; +const _drag_start_timeout = {}; + +window.onmousedown = (evn) => { + const time = new Date(); + + // Processes for a named button + const onhold = (key) => () => { + if (_double_click_timeout[key]) { + // ondclick event + ['window', 'canvas', 'world'].forEach((ctx) => + mouse.listen[ctx][key].ondclick.emit({ + buttonId: evn.button, + x: mouse[ctx].pos.x, + y: mouse[ctx].pos.y, + timestamp: new Date(), + }) + ); + } else { + // Start timer + _double_click_timeout[key] = setTimeout( + () => delete _double_click_timeout[key], + inputConfig.dClickTiming + ); + } + + // Set drag start timeout + _drag_start_timeout[key] = setTimeout(() => { + ['window', 'canvas', 'world'].forEach((ctx) => { + mouse.listen[ctx][key].ondragstart.emit({ + buttonId: evn.button, + x: mouse[ctx].pos.x, + y: mouse[ctx].pos.y, + timestamp: new Date(), + }); + if (mouse[ctx].dragging[key]) + mouse[ctx].dragging[key].drag = true; + + delete _drag_start_timeout[key]; + }); + }, inputConfig.clickTiming); + + ['window', 'canvas', 'world'].forEach((ctx) => { + mouse.buttons[key] = time; + mouse[ctx].dragging[key] = {}; + Object.assign(mouse[ctx].dragging[key], mouse[ctx].pos); + + // onpaintstart event + mouse.listen[ctx][key].onpaintstart.emit({ + buttonId: evn.button, + x: mouse[ctx].pos.x, + y: mouse[ctx].pos.y, + timestamp: new Date(), + }); + }); + }; + + // Runs the correct handler + const buttons = [onhold('left'), onhold('middle'), onhold('right')]; + + buttons[evn.button] && buttons[evn.button](); +}; + +window.onmouseup = (evn) => { + const time = new Date(); + + // Processes for a named button + const onrelease = (key) => () => { + ['window', 'canvas', 'world'].forEach((ctx) => { + const start = { + x: mouse[ctx].dragging[key].x, + y: mouse[ctx].dragging[key].y, + }; + + // onclick event + const dx = mouse[ctx].pos.x - start.x; + const dy = mouse[ctx].pos.y - start.y; + + if ( + time.getTime() - mouse.buttons[key].getTime() < + inputConfig.clickTiming && + dx * dx + dy * dy < + inputConfig.clickRadius * inputConfig.clickRadius + ) + mouse.listen[ctx][key].onclick.emit({ + buttonId: evn.button, + x: mouse[ctx].pos.x, + y: mouse[ctx].pos.y, + timestamp: new Date(), + }); + + // onpaintend event + mouse.listen[ctx][key].onpaintend.emit({ + buttonId: evn.button, + x: mouse[ctx].pos.x, + y: mouse[ctx].pos.y, + timestamp: new Date(), + }); + + // ondragend event + if (mouse[ctx].dragging[key].drag) + mouse.listen[ctx][key].ondragend.emit({ + buttonId: evn.button, + x: mouse[ctx].pos.x, + y: mouse[ctx].pos.y, + timestamp: new Date(), + }); + + mouse[ctx].dragging[key] = null; + }); + + if (_drag_start_timeout[key] !== undefined) { + clearTimeout(_drag_start_timeout[key]); + delete _drag_start_timeout[key]; + } + mouse.buttons[key] = null; + }; + + // Runs the correct handler + const buttons = [ + onrelease('left'), + onrelease('middle'), + onrelease('right'), + ]; + + buttons[evn.button] && buttons[evn.button](); +}; + +window.onmousemove = (evn) => { + // Set Window Coordinates + Object.assign(mouse.window.prev, mouse.window.pos); + mouse.window.pos = { x: evn.clientX, y: evn.clientY }; + + // Set Canvas Coordinates (using overlay canvas as reference) + if (evn.target.id === 'overlayCanvas') { + Object.assign(mouse.canvas.prev, mouse.canvas.pos); + mouse.canvas.pos = { x: evn.layerX, y: evn.layerY }; + } + + // Set World Coordinates (For now the same as canvas coords; Will be useful with infinite canvas) + if (evn.target.id === 'overlayCanvas') { + Object.assign(mouse.world.prev, mouse.world.pos); + mouse.world.pos = { x: evn.layerX, y: evn.layerY }; + } + + ['window', 'canvas', 'world'].forEach((ctx) => { + ['left', 'middle', 'right'].forEach((key) => { + // ondrag event + if (mouse[ctx].dragging[key] && mouse[ctx].dragging[key].drag) + mouse.listen[ctx][key].ondrag.emit({ + px: mouse[ctx].prev.x, + py: mouse[ctx].prev.x, + x: mouse[ctx].pos.x, + y: mouse[ctx].pos.y, + timestamp: new Date(), + }); + + // onpaint event + if (mouse[ctx].dragging[key]) + mouse.listen[ctx][key].onpaint.emit({ + px: mouse[ctx].prev.x, + py: mouse[ctx].prev.x, + x: mouse[ctx].pos.x, + y: mouse[ctx].pos.y, + timestamp: new Date(), + }); + }); + }); +}; +/** MOUSE DEBUG */ +/* +mouse.listen.window.right.onclick.on(() => + console.debug('mouse.listen.window.right.onclick') +); + +mouse.listen.window.right.ondclick.on(() => + console.debug('mouse.listen.window.right.ondclick') +); +mouse.listen.window.right.ondragstart.on(() => + console.debug('mouse.listen.window.right.ondragstart') +); +mouse.listen.window.right.ondrag.on(() => + console.debug('mouse.listen.window.right.ondrag') +); +mouse.listen.window.right.ondragend.on(() => + console.debug('mouse.listen.window.right.ondragend') +); + +mouse.listen.window.right.onpaintstart.on(() => + console.debug('mouse.listen.window.right.onpaintstart') +); +mouse.listen.window.right.onpaint.on(() => + console.debug('mouse.listen.window.right.onpaint') +); +mouse.listen.window.right.onpaintend.on(() => + console.debug('mouse.listen.window.right.onpaintend') +); +*/ diff --git a/js/util.js b/js/util.js new file mode 100644 index 0000000..c1c1eb0 --- /dev/null +++ b/js/util.js @@ -0,0 +1,27 @@ +/** + * Implementation of a simple Oberver Pattern for custom event handling + */ +function Observer() { + this.handlers = new Set(); +} + +Observer.prototype = { + // Adds handler for this message + on(callback) { + this.handlers.add(callback); + return callback; + }, + clear(callback) { + return this.handlers.delete(callback); + }, + emit(msg) { + this.handlers.forEach(async (handler) => { + try { + await handler(msg); + } catch (e) { + console.warn('Observer failed to run handler'); + console.warn(handler); + } + }); + }, +}; From 217b89efef4bc8690b62b3197a2343aae5f5bf3e Mon Sep 17 00:00:00 2001 From: Victor Seiji Hariki Date: Sun, 20 Nov 2022 23:23:08 -0300 Subject: [PATCH 17/22] fix prev field for drag events and add onmousemove Signed-off-by: Victor Seiji Hariki a Signed-off-by: Victor Seiji Hariki --- js/index.js | 763 ++++++++++++++++++++++++++++++++++------------------ js/input.js | 16 +- 2 files changed, 510 insertions(+), 269 deletions(-) diff --git a/js/index.js b/js/index.js index 8cf2559..0e6e679 100644 --- a/js/index.js +++ b/js/index.js @@ -3,12 +3,13 @@ window.onload = startup; -var stableDiffusionData = { //includes img2img data but works for txt2img just fine - prompt: "", - negative_prompt: "", +var stableDiffusionData = { + //includes img2img data but works for txt2img just fine + prompt: '', + negative_prompt: '', seed: -1, cfg_scale: null, - sampler_index: "DDIM", + sampler_index: 'DDIM', steps: null, denoising_strength: 1, mask_blur: 0, @@ -16,7 +17,7 @@ var stableDiffusionData = { //includes img2img data but works for txt2img just f width: 512, height: 512, n_iter: null, // batch count - mask: "", + mask: '', init_images: [], inpaint_full_res: false, inpainting_fill: 2, @@ -35,7 +36,7 @@ var stableDiffusionData = { //includes img2img data but works for txt2img just f // "init_images": [ // imageS!??!? wtf maybe for batch img2img?? i just dump one base64 in here // "string" // ], - // "resize_mode": 0, + // "resize_mode": 0, // "denoising_strength": 0.75, // yeah see // "mask": "string", // string is just a base64 image // "mask_blur": 4, @@ -44,13 +45,19 @@ var stableDiffusionData = { //includes img2img data but works for txt2img just f // "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]) { +function sliderChangeHandlerFactory( + 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); @@ -63,17 +70,18 @@ function sliderChangeHandlerFactory(sliderId, textBoxId, dataKey, defaultV, sett if (value) setter(dataKey, value); - if (!eventSource || eventSource.id === textBoxId) sliderEl.value = getter(dataKey); - setter(dataKey, Number(sliderEl.value)) + if (!eventSource || eventSource.id === textBoxId) + sliderEl.value = getter(dataKey); + setter(dataKey, Number(sliderEl.value)); textBoxEl.value = 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 @@ -81,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; @@ -103,7 +111,7 @@ var snapToGrid = true; var paintMode = false; var eraseMode = false; //TODO this is broken, functionality still exists in code but UI element is just naively disabled var backupMaskPaintCanvas; //??? -var backupMaskPaintCtx; //...? look i am bad at this +var backupMaskPaintCtx; //...? look i am bad at this var backupMaskChunk = null; var backupMaskX = null; var backupMaskY = null; @@ -119,27 +127,27 @@ var placingArbitraryImage = false; // for when the user has loaded an existing i var enableErasing = false; // accidental right-click erase if the user isn't trying to erase is a bad thing // 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(); @@ -158,17 +166,17 @@ function startup() { changeOverMaskPx(); changeHiResFix(); changeEnableErasing(); - document.getElementById("overlayCanvas").onmousemove = mouseMove; - document.getElementById("overlayCanvas").onmousedown = mouseDown; - document.getElementById("overlayCanvas").onmouseup = mouseUp; - document.getElementById("scaleFactor").value = scaleFactor; + 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) - } + writeArbitraryImage(img, imageParams.x, imageParams.y); + }; img.src = arbitraryImageBase64; } @@ -180,7 +188,7 @@ function writeArbitraryImage(img, x, y) { }); blockNewImages = false; placingArbitraryImage = false; - document.getElementById("preloadImage").files = null; + document.getElementById('preloadImage').files = null; } function dream(x, y, prompt) { @@ -188,19 +196,25 @@ function dream(x, y, prompt) { 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); - }); + 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; + 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. @@ -208,12 +222,12 @@ async function postData(promptData) { 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' + 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 + body: JSON.stringify(promptData), // body data type must match "Content-Type" header }); return response.json(); // parses JSON response into native JavaScript objects } @@ -222,22 +236,24 @@ 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; - } + 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]; + img.src = 'data:image/png;base64,' + returnedImages[imageIndex]; } function accept(evt) { @@ -273,7 +289,7 @@ function prevImg(evt) { } function nextImg(evt) { - if (imageIndex == (totalImagesReturned - 1)) { + if (imageIndex == totalImagesReturned - 1) { imageIndex = -1; } changeImg(true); @@ -284,8 +300,8 @@ function changeImg(forward) { 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"); + }; + var tmpIndex = document.getElementById('currentImgIndex'); if (forward) { imageIndex++; } else { @@ -293,11 +309,11 @@ function changeImg(forward) { } 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 + 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"); + const element = document.getElementById('veryTempDiv'); element.remove(); tempCtx.clearRect(0, 0, tempCanvas.width, tempCanvas.height); } @@ -306,13 +322,17 @@ 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); + var iData = new ImageData( + backupMaskChunk.data, + backupMaskChunk.height, + backupMaskChunk.width + ); maskPaintCtx.putImageData(iData, backupMaskX, backupMaskY); } } function clearBackupMask() { - // clear backupmask + // clear backupmask backupMaskChunk = null; backupMaskX = null; backupMaskY = null; @@ -340,15 +360,14 @@ function placeImage() { }); tmpImgXYWH = {}; returnedImages = null; - } + }; // load the image data after defining the closure - img.src = "data:image/png;base64," + returnedImages[imageIndex]; - + img.src = 'data:image/png;base64,' + returnedImages[imageIndex]; } function sleep(ms) { // what was this even for, anyway? - return new Promise(resolve => setTimeout(resolve, ms)); + return new Promise((resolve) => setTimeout(resolve, ms)); } function snap(i, scaled = true) { @@ -357,19 +376,18 @@ function snap(i, scaled = true) { if (scaled) { if (scaleFactor % 2 != 0) { // odd number, snaps to center of cell, oops - scaleOffset = (basePixelCount / 2); + scaleOffset = basePixelCount / 2; } } - var snapOffset = i % basePixelCount - scaleOffset; + var snapOffset = (i % basePixelCount) - scaleOffset; if (snapOffset == 0) { return snapOffset; } return -snapOffset; } - function mouseMove(evt) { - const rect = ovCanvas.getBoundingClientRect() // not-quite pixel offset was driving me insane + const rect = ovCanvas.getBoundingClientRect(); // not-quite pixel offset was driving me insane const canvasOffsetX = rect.left; const canvasOffsetY = rect.top; heldButton = evt.buttons; @@ -380,7 +398,7 @@ function mouseMove(evt) { 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 + 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; @@ -394,7 +412,7 @@ function mouseMove(evt) { ovCtx.drawImage(arbitraryImage, finalX, finalY); } else if (!paintMode) { // draw targeting square reticle thingy cursor - ovCtx.strokeStyle = "#00000077"; + ovCtx.strokeStyle = '#00000077'; snapOffsetX = 0; snapOffsetY = 0; if (snapToGrid) { @@ -403,59 +421,71 @@ function mouseMove(evt) { } 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 - } else { - // draw big translucent red blob cursor - ovCtx.beginPath(); - ovCtx.arc(canvasX, canvasY, 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(); - // in case i'm trying to draw - mouseX = parseInt(evt.clientX - canvasOffsetX); - mouseY = parseInt(evt.clientY - canvasOffsetY); - if (clicked) { - // i'm trying to draw, please draw :( - if (eraseMode) { - // THIS IS SOOOO BROKEN AND I DON'T UNDERSTAND WHY BECAUSE I AM THE BIG DUMB - maskPaintCtx.globalCompositeOperation = 'destination-out'; - // maskPaintCtx.strokeStyle = "#FFFFFF00"; - } else { - maskPaintCtx.globalCompositeOperation = 'source-over'; - maskPaintCtx.strokeStyle = "#FF6A6A10"; - } - - maskPaintCtx.lineWidth = 8 * scaleFactor; - maskPaintCtx.beginPath(); - maskPaintCtx.moveTo(prevMouseX, prevMouseY); - maskPaintCtx.lineTo(mouseX, mouseY); - maskPaintCtx.lineJoin = maskPaintCtx.lineCap = 'round'; - maskPaintCtx.stroke(); - } - // Erase mask if right button is held - // no reason to have to tick a checkbox for this, more intuitive for both erases (mask and actual images) to just work on right click and inform the user about it - if (evt.buttons == 2) { - maskPaintCtx.globalCompositeOperation = 'destination-out'; - maskPaintCtx.beginPath(); - maskPaintCtx.strokeStyle = "#FFFFFFFF"; - maskPaintCtx.lineWidth = 8 * scaleFactor; - maskPaintCtx.moveTo(prevMouseX, prevMouseY); - maskPaintCtx.lineTo(mouseX, mouseY); - maskPaintCtx.lineJoin = maskPaintCtx.lineCap = 'round'; - maskPaintCtx.stroke(); - - } - prevMouseX = mouseX; - prevMouseY = mouseY; + ovCtx.strokeRect( + parseInt(finalX - (basePixelCount * scaleFactor) / 2), + parseInt(finalY - (basePixelCount * scaleFactor) / 2), + basePixelCount * scaleFactor, + basePixelCount * scaleFactor + ); //origin is middle of the frame } } +mouse.listen.canvas.onmousemove.on((evn) => { + if (paintMode) { + // 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) { + 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(); + } +}); + +mouse.listen.canvas.right.onpaint.on((evn) => { + if (paintMode) { + 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 = '#00FF00'; + ovCtx.fill(); + + ovCtx.beginPath(); + ovCtx.arc(evn.px, evn.py, 4 * scaleFactor, 0, 2 * Math.PI, true); // for some reason 4x on an arc is === to 8x on a line??? + ovCtx.fillStyle = '#0000FF'; + ovCtx.fill(); + + 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(); + } +}); + function mouseDown(evt) { - const rect = ovCanvas.getBoundingClientRect() + const rect = ovCanvas.getBoundingClientRect(); var oddOffset = 0; if (scaleFactor % 2 != 0) { oddOffset = basePixelCount / 2; } - if (evt.button == 0) { // left click + if (evt.button == 0) { + // left click if (placingArbitraryImage) { var nextBox = {}; nextBox.x = evt.offsetX; @@ -463,36 +493,53 @@ function mouseDown(evt) { nextBox.w = arbitraryImageData.width; nextBox.h = arbitraryImageData.height; dropTargets.push(nextBox); - } else if (paintMode) { - //const rect = ovCanvas.getBoundingClientRect() // not-quite pixel offset was driving me insane - const canvasOffsetX = rect.left; - const canvasOffsetY = rect.top; - prevMouseX = mouseX = evt.clientX - canvasOffsetX; - prevMouseY = mouseY = evt.clientY - canvasOffsetY; - clicked = true; - } else { + } 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.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 + 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); + 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); + ctx.clearRect( + canvasX - (basePixelCount * scaleFactor) / 2, + canvasY - (basePixelCount * scaleFactor) / 2, + basePixelCount * scaleFactor, + basePixelCount * scaleFactor + ); } } } } function mouseUp(evt) { - if (evt.button == 0) { // left click + 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? @@ -519,7 +566,7 @@ function mouseUp(evt) { //TODO seriously, refactor this blockNewImages = true; clearTargetMask(); - tgtCtx.strokeStyle = "#55000077"; + 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; @@ -543,42 +590,68 @@ function mouseUp(evt) { 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)) { + 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 + 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"); + 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"); + 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); + 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 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 + // 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 + } 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 + 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]; @@ -591,7 +664,7 @@ function mouseUp(evt) { var x, y, index; for (y = 0; y < drawIt.h; y++) { for (x = 0; x < drawIt.w; x++) { - index = ((y * drawIt.w + x) * 4); + index = (y * drawIt.w + x) * 4; if (overMaskImgData.data[index] > 0) { pix.x.push(x); pix.y.push(y); @@ -616,81 +689,159 @@ function mouseUp(evt) { // i hate uint8clampedarray and math // primarily math - // actually just my brain + // 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 + 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 + //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 + // 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); + 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 + // ok then // ensure it's not already a mask pixel - if (overMaskImgData.data[potentialPixelIndex] != 255) { + 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; + 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); + 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; + 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); + 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; + 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); + 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; + 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 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) { @@ -701,7 +852,10 @@ function mouseUp(evt) { } } // backup any painted masks ingested then them, replacable if user doesn't like resultant image - var clearArea = maskPaintCtx.createImageData(drawIt.w, drawIt.h); + var clearArea = maskPaintCtx.createImageData( + drawIt.w, + drawIt.h + ); backupMaskChunk = maskChunk; backupMaskX = drawIt.x; backupMaskY = drawIt.y; @@ -717,80 +871,113 @@ function mouseUp(evt) { initImgCanvasCtx.putImageData(initImgData, 0, 0); var initImgBase64 = initImgCanvas.toDataURL(); // anyway all that to say NOW let's run img2img - endpoint = "img2img"; + endpoint = 'img2img'; stableDiffusionData.mask = overMaskBase64; stableDiffusionData.init_images = [initImgBase64]; // slightly more involved than txt2img } else { // time to run txt2img - endpoint = "txt2img"; + endpoint = 'txt2img'; // easy enough } - stableDiffusionData.prompt = document.getElementById("prompt").value; - stableDiffusionData.negative_prompt = document.getElementById("negPrompt").value; + 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); + 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); +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; + 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); + 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); -const changeBatchSize = sliderChangeHandlerFactory("batchSize", "batchSizeText", "batch_size", 2); -const changeBatchCount = sliderChangeHandlerFactory("batchCount", "batchCountText", "n_iter", 2); +const changeCfgScale = sliderChangeHandlerFactory( + 'cfgScale', + 'cfgScaleTxt', + 'cfg_scale', + 7.0 +); +const changeBatchSize = sliderChangeHandlerFactory( + 'batchSize', + 'batchSizeText', + 'batch_size', + 2 +); +const changeBatchCount = sliderChangeHandlerFactory( + 'batchCount', + 'batchCountText', + 'n_iter', + 2 +); 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); + return !canvas + .getContext('2d') + .getImageData(x, y, w, h) + .data.some((channel) => channel !== 0); } function drawBackground() { @@ -810,16 +997,16 @@ function drawBackground() { } function preloadImage() { - // gonna legit scream - document.getElementById("overlayCanvas").onmousemove = null; - document.getElementById("overlayCanvas").onmousedown = null; - document.getElementById("overlayCanvas").onmouseup = null; + // gonna legit scream + document.getElementById('overlayCanvas').onmousemove = null; + document.getElementById('overlayCanvas').onmousedown = null; + document.getElementById('overlayCanvas').onmouseup = null; - var file = document.getElementById("preloadImage").files[0]; + 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"); + var imgCanvas = document.createElement('canvas'); + var imgCtx = imgCanvas.getContext('2d'); arbitraryImage = new Image(); arbitraryImage.onload = function () { blockNewImages = true; @@ -827,22 +1014,32 @@ function preloadImage() { 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... + 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; - } + 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'; + 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'); @@ -859,7 +1056,7 @@ function cropCanvas(sourceCanvas) { for (y = 0; y < h; y++) { for (x = 0; x < w; x++) { - // lol i need to learn what this part does + // 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) { @@ -870,15 +1067,21 @@ function cropCanvas(sourceCanvas) { } // ...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 }); + 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 cut = sourceCanvas + .getContext('2d') + .getImageData(pix.x[0], pix.y[0], w, h); var cutCanvas = document.createElement('canvas'); cutCanvas.width = w; cutCanvas.height = h; @@ -892,30 +1095,56 @@ function cropCanvas(sourceCanvas) { } 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"); + 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); + 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/input.js b/js/input.js index 98b005e..2d4cc5a 100644 --- a/js/input.js +++ b/js/input.js @@ -46,6 +46,7 @@ function _mouse_observers() { function _context_observers() { return { + onmousemove: new Observer(), left: _mouse_observers(), middle: _mouse_observers(), right: _mouse_observers(), @@ -230,12 +231,19 @@ window.onmousemove = (evn) => { } ['window', 'canvas', 'world'].forEach((ctx) => { + mouse.listen[ctx].onmousemove.emit({ + px: mouse[ctx].prev.x, + py: mouse[ctx].prev.y, + x: mouse[ctx].pos.x, + y: mouse[ctx].pos.y, + timestamp: new Date(), + }); ['left', 'middle', 'right'].forEach((key) => { // ondrag event if (mouse[ctx].dragging[key] && mouse[ctx].dragging[key].drag) mouse.listen[ctx][key].ondrag.emit({ px: mouse[ctx].prev.x, - py: mouse[ctx].prev.x, + py: mouse[ctx].prev.y, x: mouse[ctx].pos.x, y: mouse[ctx].pos.y, timestamp: new Date(), @@ -245,7 +253,7 @@ window.onmousemove = (evn) => { if (mouse[ctx].dragging[key]) mouse.listen[ctx][key].onpaint.emit({ px: mouse[ctx].prev.x, - py: mouse[ctx].prev.x, + py: mouse[ctx].prev.y, x: mouse[ctx].pos.x, y: mouse[ctx].pos.y, timestamp: new Date(), @@ -282,3 +290,7 @@ mouse.listen.window.right.onpaintend.on(() => console.debug('mouse.listen.window.right.onpaintend') ); */ + +/** + * Mouse input processing + */ From d6435abf2e74992d5693d54ca5833a040cb34802 Mon Sep 17 00:00:00 2001 From: Victor Seiji Hariki Date: Sun, 20 Nov 2022 23:29:50 -0300 Subject: [PATCH 18/22] solid color mask and migration to new input method reimplements mask using the new input lib, makes mask use a solid color (masks shouldn't become darker the more you draw over itself) and the mask layer is now rendered with the alpha using css. Signed-off-by: Victor Seiji Hariki --- css/index.css | 23 ++++++++++++----------- js/index.js | 13 +++---------- 2 files changed, 15 insertions(+), 21 deletions(-) diff --git a/css/index.css b/css/index.css index bd2f317..6f23d25 100644 --- a/css/index.css +++ b/css/index.css @@ -12,23 +12,23 @@ } .maskPaintCanvas { - border: 3px dotted #993355C0 + border: 3px dotted #993355c0; } .overlayCanvas { - border: 1px solid #F00; + border: 1px solid #f00; } .tempCanvas { - border: 3px dotted #007AFFC0; + border: 3px dotted #007affc0; } .targetCanvas { - border: 2px dashed #0F0; + border: 2px dashed #0f0; } .canvas { - border: 2px dotted #00F; + border: 2px dotted #00f; } .mainHSplit { @@ -45,7 +45,6 @@ grid-template-rows: 1fr; grid-column-gap: 5px; grid-row-gap: 5px; - } #infoContainer { @@ -53,10 +52,9 @@ width: 250px; height: auto; z-index: 999; - } -#draggable{ - cursor:move +#draggable { + cursor: move; } #DraggableTitleBar { @@ -153,7 +151,6 @@ button.tool:hover { cursor: auto; } - .canvasHolder { position: relative; width: 2560px; @@ -180,8 +177,12 @@ button.tool:hover { position: absolute; } +.maskPaintCanvas { + filter: opacity(40%); +} + .strokeText { -webkit-text-stroke: 1px #888; font-size: 150%; color: #000; -} \ No newline at end of file +} diff --git a/js/index.js b/js/index.js index 0e6e679..5cb636f 100644 --- a/js/index.js +++ b/js/index.js @@ -430,6 +430,9 @@ function mouseMove(evt) { } } +/** + * Mask implementation + */ mouse.listen.canvas.onmousemove.on((evn) => { if (paintMode) { // draw big translucent red blob cursor @@ -456,16 +459,6 @@ mouse.listen.canvas.left.onpaint.on((evn) => { mouse.listen.canvas.right.onpaint.on((evn) => { if (paintMode) { - 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 = '#00FF00'; - ovCtx.fill(); - - ovCtx.beginPath(); - ovCtx.arc(evn.px, evn.py, 4 * scaleFactor, 0, 2 * Math.PI, true); // for some reason 4x on an arc is === to 8x on a line??? - ovCtx.fillStyle = '#0000FF'; - ovCtx.fill(); - maskPaintCtx.globalCompositeOperation = 'destination-out'; maskPaintCtx.strokeStyle = '#FFFFFFFF'; From c27da0f51a8b1687a57983c0370fbbb3f3f07198 Mon Sep 17 00:00:00 2001 From: Victor Seiji Hariki Date: Sun, 20 Nov 2022 23:42:07 -0300 Subject: [PATCH 19/22] add target info to events and fix mask impl Signed-off-by: Victor Seiji Hariki --- index.html | 3 --- js/index.js | 7 +++---- js/input.js | 9 +++++++++ 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/index.html b/index.html index ed2ec20..a8e525c 100644 --- a/index.html +++ b/index.html @@ -69,9 +69,6 @@

- -
diff --git a/js/index.js b/js/index.js index 5cb636f..82d01ea 100644 --- a/js/index.js +++ b/js/index.js @@ -109,7 +109,6 @@ const basePixelCount = 64; //64 px - ALWAYS 64 PX var scaleFactor = 8; //x64 px var snapToGrid = true; var paintMode = false; -var eraseMode = false; //TODO this is broken, functionality still exists in code but UI element is just naively disabled var backupMaskPaintCanvas; //??? var backupMaskPaintCtx; //...? look i am bad at this var backupMaskChunk = null; @@ -434,7 +433,7 @@ function mouseMove(evt) { * Mask implementation */ mouse.listen.canvas.onmousemove.on((evn) => { - if (paintMode) { + 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??? @@ -444,7 +443,7 @@ mouse.listen.canvas.onmousemove.on((evn) => { }); mouse.listen.canvas.left.onpaint.on((evn) => { - if (paintMode) { + if (paintMode && evn.target.id === 'overlayCanvas') { maskPaintCtx.globalCompositeOperation = 'source-over'; maskPaintCtx.strokeStyle = '#FF6A6A'; @@ -458,7 +457,7 @@ mouse.listen.canvas.left.onpaint.on((evn) => { }); mouse.listen.canvas.right.onpaint.on((evn) => { - if (paintMode) { + if (paintMode && evn.target.id === 'overlayCanvas') { maskPaintCtx.globalCompositeOperation = 'destination-out'; maskPaintCtx.strokeStyle = '#FFFFFFFF'; diff --git a/js/input.js b/js/input.js index 2d4cc5a..095ea1f 100644 --- a/js/input.js +++ b/js/input.js @@ -97,6 +97,7 @@ window.onmousedown = (evn) => { // ondclick event ['window', 'canvas', 'world'].forEach((ctx) => mouse.listen[ctx][key].ondclick.emit({ + target: evn.target, buttonId: evn.button, x: mouse[ctx].pos.x, y: mouse[ctx].pos.y, @@ -115,6 +116,7 @@ window.onmousedown = (evn) => { _drag_start_timeout[key] = setTimeout(() => { ['window', 'canvas', 'world'].forEach((ctx) => { mouse.listen[ctx][key].ondragstart.emit({ + target: evn.target, buttonId: evn.button, x: mouse[ctx].pos.x, y: mouse[ctx].pos.y, @@ -134,6 +136,7 @@ window.onmousedown = (evn) => { // onpaintstart event mouse.listen[ctx][key].onpaintstart.emit({ + target: evn.target, buttonId: evn.button, x: mouse[ctx].pos.x, y: mouse[ctx].pos.y, @@ -170,6 +173,7 @@ window.onmouseup = (evn) => { inputConfig.clickRadius * inputConfig.clickRadius ) mouse.listen[ctx][key].onclick.emit({ + target: evn.target, buttonId: evn.button, x: mouse[ctx].pos.x, y: mouse[ctx].pos.y, @@ -178,6 +182,7 @@ window.onmouseup = (evn) => { // onpaintend event mouse.listen[ctx][key].onpaintend.emit({ + target: evn.target, buttonId: evn.button, x: mouse[ctx].pos.x, y: mouse[ctx].pos.y, @@ -187,6 +192,7 @@ window.onmouseup = (evn) => { // ondragend event if (mouse[ctx].dragging[key].drag) mouse.listen[ctx][key].ondragend.emit({ + target: evn.target, buttonId: evn.button, x: mouse[ctx].pos.x, y: mouse[ctx].pos.y, @@ -232,6 +238,7 @@ window.onmousemove = (evn) => { ['window', 'canvas', 'world'].forEach((ctx) => { mouse.listen[ctx].onmousemove.emit({ + target: evn.target, px: mouse[ctx].prev.x, py: mouse[ctx].prev.y, x: mouse[ctx].pos.x, @@ -242,6 +249,7 @@ window.onmousemove = (evn) => { // ondrag event if (mouse[ctx].dragging[key] && mouse[ctx].dragging[key].drag) mouse.listen[ctx][key].ondrag.emit({ + target: evn.target, px: mouse[ctx].prev.x, py: mouse[ctx].prev.y, x: mouse[ctx].pos.x, @@ -252,6 +260,7 @@ window.onmousemove = (evn) => { // onpaint event if (mouse[ctx].dragging[key]) mouse.listen[ctx][key].onpaint.emit({ + target: evn.target, px: mouse[ctx].prev.x, py: mouse[ctx].prev.y, x: mouse[ctx].pos.x, From 80c446da3d0800e40631d673957e689778dd33dd Mon Sep 17 00:00:00 2001 From: Victor Seiji Hariki Date: Mon, 21 Nov 2022 00:02:25 -0300 Subject: [PATCH 20/22] format input.js according to new guidelines Signed-off-by: Victor Seiji Hariki --- js/input.js | 422 ++++++++++++++++++++++++++-------------------------- 1 file changed, 208 insertions(+), 214 deletions(-) diff --git a/js/input.js b/js/input.js index 095ea1f..6a76c2e 100644 --- a/js/input.js +++ b/js/input.js @@ -1,7 +1,7 @@ const inputConfig = { - clickRadius: 10, // Radius to be considered a click (pixels). If farther, turns into a drag - clickTiming: 500, // Timing window to be considered a click (ms). If longer, turns into a drag - dClickTiming: 500, // Timing window to be considered a double click (ms). + clickRadius: 10, // Radius to be considered a click (pixels). If farther, turns into a drag + clickTiming: 500, // Timing window to be considered a click (ms). If longer, turns into a drag + dClickTiming: 500, // Timing window to be considered a double click (ms). }; /** @@ -9,266 +9,260 @@ const inputConfig = { */ // Base object generator functions function _context_coords() { - return { - dragging: { - left: null, - middle: null, - right: null, - }, + return { + dragging: { + left: null, + middle: null, + right: null, + }, - prev: { - x: 0, - y: 0, - }, + prev: { + x: 0, + y: 0, + }, - pos: { - x: 0, - y: 0, - }, - }; + pos: { + x: 0, + y: 0, + }, + }; } function _mouse_observers() { - return { - // Simple click handlers - onclick: new Observer(), - // Double click handlers (will still trigger simple click handler as well) - ondclick: new Observer(), - // Drag handler - ondragstart: new Observer(), - ondrag: new Observer(), - ondragend: new Observer(), - // Paint handler (like drag handler, but with no delay); will trigger during clicks too - onpaintstart: new Observer(), - onpaint: new Observer(), - onpaintend: new Observer(), - }; + return { + // Simple click handlers + onclick: new Observer(), + // Double click handlers (will still trigger simple click handler as well) + ondclick: new Observer(), + // Drag handler + ondragstart: new Observer(), + ondrag: new Observer(), + ondragend: new Observer(), + // Paint handler (like drag handler, but with no delay); will trigger during clicks too + onpaintstart: new Observer(), + onpaint: new Observer(), + onpaintend: new Observer(), + }; } function _context_observers() { - return { - onmousemove: new Observer(), - left: _mouse_observers(), - middle: _mouse_observers(), - right: _mouse_observers(), - }; + return { + onmousemove: new Observer(), + left: _mouse_observers(), + middle: _mouse_observers(), + right: _mouse_observers(), + }; } const mouse = { - buttons: { - right: null, - left: null, - middle: null, - }, + buttons: { + right: null, + left: null, + middle: null, + }, - // Mouse Actions in Window Coordinates - window: _context_coords(), + // Mouse Actions in Window Coordinates + window: _context_coords(), - // Mouse Actions in Canvas Coordinates - canvas: _context_coords(), + // Mouse Actions in Canvas Coordinates + canvas: _context_coords(), - // Mouse Actions in World Coordinates - world: _context_coords(), + // Mouse Actions in World Coordinates + world: _context_coords(), - listen: { - window: _context_observers(), - canvas: _context_observers(), - world: _context_observers(), - }, + listen: { + window: _context_observers(), + canvas: _context_observers(), + world: _context_observers(), + }, }; function _mouse_state_snapshot() { - return { - buttons: window.structuredClone(mouse.buttons), - window: window.structuredClone(mouse.window), - canvas: window.structuredClone(mouse.canvas), - world: window.structuredClone(mouse.world), - }; + return { + buttons: window.structuredClone(mouse.buttons), + window: window.structuredClone(mouse.window), + canvas: window.structuredClone(mouse.canvas), + world: window.structuredClone(mouse.world), + }; } const _double_click_timeout = {}; const _drag_start_timeout = {}; window.onmousedown = (evn) => { - const time = new Date(); + const time = new Date(); - // Processes for a named button - const onhold = (key) => () => { - if (_double_click_timeout[key]) { - // ondclick event - ['window', 'canvas', 'world'].forEach((ctx) => - mouse.listen[ctx][key].ondclick.emit({ - target: evn.target, - buttonId: evn.button, - x: mouse[ctx].pos.x, - y: mouse[ctx].pos.y, - timestamp: new Date(), - }) - ); - } else { - // Start timer - _double_click_timeout[key] = setTimeout( - () => delete _double_click_timeout[key], - inputConfig.dClickTiming - ); - } + // Processes for a named button + const onhold = (key) => () => { + if (_double_click_timeout[key]) { + // ondclick event + ["window", "canvas", "world"].forEach((ctx) => + mouse.listen[ctx][key].ondclick.emit({ + target: evn.target, + buttonId: evn.button, + x: mouse[ctx].pos.x, + y: mouse[ctx].pos.y, + timestamp: new Date(), + }) + ); + } else { + // Start timer + _double_click_timeout[key] = setTimeout( + () => delete _double_click_timeout[key], + inputConfig.dClickTiming + ); + } - // Set drag start timeout - _drag_start_timeout[key] = setTimeout(() => { - ['window', 'canvas', 'world'].forEach((ctx) => { - mouse.listen[ctx][key].ondragstart.emit({ - target: evn.target, - buttonId: evn.button, - x: mouse[ctx].pos.x, - y: mouse[ctx].pos.y, - timestamp: new Date(), - }); - if (mouse[ctx].dragging[key]) - mouse[ctx].dragging[key].drag = true; + // Set drag start timeout + _drag_start_timeout[key] = setTimeout(() => { + ["window", "canvas", "world"].forEach((ctx) => { + mouse.listen[ctx][key].ondragstart.emit({ + target: evn.target, + buttonId: evn.button, + x: mouse[ctx].pos.x, + y: mouse[ctx].pos.y, + timestamp: new Date(), + }); + if (mouse[ctx].dragging[key]) mouse[ctx].dragging[key].drag = true; - delete _drag_start_timeout[key]; - }); - }, inputConfig.clickTiming); + delete _drag_start_timeout[key]; + }); + }, inputConfig.clickTiming); - ['window', 'canvas', 'world'].forEach((ctx) => { - mouse.buttons[key] = time; - mouse[ctx].dragging[key] = {}; - Object.assign(mouse[ctx].dragging[key], mouse[ctx].pos); + ["window", "canvas", "world"].forEach((ctx) => { + mouse.buttons[key] = time; + mouse[ctx].dragging[key] = {}; + Object.assign(mouse[ctx].dragging[key], mouse[ctx].pos); - // onpaintstart event - mouse.listen[ctx][key].onpaintstart.emit({ - target: evn.target, - buttonId: evn.button, - x: mouse[ctx].pos.x, - y: mouse[ctx].pos.y, - timestamp: new Date(), - }); - }); - }; + // onpaintstart event + mouse.listen[ctx][key].onpaintstart.emit({ + target: evn.target, + buttonId: evn.button, + x: mouse[ctx].pos.x, + y: mouse[ctx].pos.y, + timestamp: new Date(), + }); + }); + }; - // Runs the correct handler - const buttons = [onhold('left'), onhold('middle'), onhold('right')]; + // Runs the correct handler + const buttons = [onhold("left"), onhold("middle"), onhold("right")]; - buttons[evn.button] && buttons[evn.button](); + buttons[evn.button] && buttons[evn.button](); }; window.onmouseup = (evn) => { - const time = new Date(); + const time = new Date(); - // Processes for a named button - const onrelease = (key) => () => { - ['window', 'canvas', 'world'].forEach((ctx) => { - const start = { - x: mouse[ctx].dragging[key].x, - y: mouse[ctx].dragging[key].y, - }; + // Processes for a named button + const onrelease = (key) => () => { + ["window", "canvas", "world"].forEach((ctx) => { + const start = { + x: mouse[ctx].dragging[key].x, + y: mouse[ctx].dragging[key].y, + }; - // onclick event - const dx = mouse[ctx].pos.x - start.x; - const dy = mouse[ctx].pos.y - start.y; + // onclick event + const dx = mouse[ctx].pos.x - start.x; + const dy = mouse[ctx].pos.y - start.y; - if ( - time.getTime() - mouse.buttons[key].getTime() < - inputConfig.clickTiming && - dx * dx + dy * dy < - inputConfig.clickRadius * inputConfig.clickRadius - ) - mouse.listen[ctx][key].onclick.emit({ - target: evn.target, - buttonId: evn.button, - x: mouse[ctx].pos.x, - y: mouse[ctx].pos.y, - timestamp: new Date(), - }); + if ( + time.getTime() - mouse.buttons[key].getTime() < + inputConfig.clickTiming && + dx * dx + dy * dy < inputConfig.clickRadius * inputConfig.clickRadius + ) + mouse.listen[ctx][key].onclick.emit({ + target: evn.target, + buttonId: evn.button, + x: mouse[ctx].pos.x, + y: mouse[ctx].pos.y, + timestamp: new Date(), + }); - // onpaintend event - mouse.listen[ctx][key].onpaintend.emit({ - target: evn.target, - buttonId: evn.button, - x: mouse[ctx].pos.x, - y: mouse[ctx].pos.y, - timestamp: new Date(), - }); + // onpaintend event + mouse.listen[ctx][key].onpaintend.emit({ + target: evn.target, + buttonId: evn.button, + x: mouse[ctx].pos.x, + y: mouse[ctx].pos.y, + timestamp: new Date(), + }); - // ondragend event - if (mouse[ctx].dragging[key].drag) - mouse.listen[ctx][key].ondragend.emit({ - target: evn.target, - buttonId: evn.button, - x: mouse[ctx].pos.x, - y: mouse[ctx].pos.y, - timestamp: new Date(), - }); + // ondragend event + if (mouse[ctx].dragging[key].drag) + mouse.listen[ctx][key].ondragend.emit({ + target: evn.target, + buttonId: evn.button, + x: mouse[ctx].pos.x, + y: mouse[ctx].pos.y, + timestamp: new Date(), + }); - mouse[ctx].dragging[key] = null; - }); + mouse[ctx].dragging[key] = null; + }); - if (_drag_start_timeout[key] !== undefined) { - clearTimeout(_drag_start_timeout[key]); - delete _drag_start_timeout[key]; - } - mouse.buttons[key] = null; - }; + if (_drag_start_timeout[key] !== undefined) { + clearTimeout(_drag_start_timeout[key]); + delete _drag_start_timeout[key]; + } + mouse.buttons[key] = null; + }; - // Runs the correct handler - const buttons = [ - onrelease('left'), - onrelease('middle'), - onrelease('right'), - ]; + // Runs the correct handler + const buttons = [onrelease("left"), onrelease("middle"), onrelease("right")]; - buttons[evn.button] && buttons[evn.button](); + buttons[evn.button] && buttons[evn.button](); }; window.onmousemove = (evn) => { - // Set Window Coordinates - Object.assign(mouse.window.prev, mouse.window.pos); - mouse.window.pos = { x: evn.clientX, y: evn.clientY }; + // Set Window Coordinates + Object.assign(mouse.window.prev, mouse.window.pos); + mouse.window.pos = {x: evn.clientX, y: evn.clientY}; - // Set Canvas Coordinates (using overlay canvas as reference) - if (evn.target.id === 'overlayCanvas') { - Object.assign(mouse.canvas.prev, mouse.canvas.pos); - mouse.canvas.pos = { x: evn.layerX, y: evn.layerY }; - } + // Set Canvas Coordinates (using overlay canvas as reference) + if (evn.target.id === "overlayCanvas") { + Object.assign(mouse.canvas.prev, mouse.canvas.pos); + mouse.canvas.pos = {x: evn.layerX, y: evn.layerY}; + } - // Set World Coordinates (For now the same as canvas coords; Will be useful with infinite canvas) - if (evn.target.id === 'overlayCanvas') { - Object.assign(mouse.world.prev, mouse.world.pos); - mouse.world.pos = { x: evn.layerX, y: evn.layerY }; - } + // Set World Coordinates (For now the same as canvas coords; Will be useful with infinite canvas) + if (evn.target.id === "overlayCanvas") { + Object.assign(mouse.world.prev, mouse.world.pos); + mouse.world.pos = {x: evn.layerX, y: evn.layerY}; + } - ['window', 'canvas', 'world'].forEach((ctx) => { - mouse.listen[ctx].onmousemove.emit({ - target: evn.target, - px: mouse[ctx].prev.x, - py: mouse[ctx].prev.y, - x: mouse[ctx].pos.x, - y: mouse[ctx].pos.y, - timestamp: new Date(), - }); - ['left', 'middle', 'right'].forEach((key) => { - // ondrag event - if (mouse[ctx].dragging[key] && mouse[ctx].dragging[key].drag) - mouse.listen[ctx][key].ondrag.emit({ - target: evn.target, - px: mouse[ctx].prev.x, - py: mouse[ctx].prev.y, - x: mouse[ctx].pos.x, - y: mouse[ctx].pos.y, - timestamp: new Date(), - }); + ["window", "canvas", "world"].forEach((ctx) => { + mouse.listen[ctx].onmousemove.emit({ + target: evn.target, + px: mouse[ctx].prev.x, + py: mouse[ctx].prev.y, + x: mouse[ctx].pos.x, + y: mouse[ctx].pos.y, + timestamp: new Date(), + }); + ["left", "middle", "right"].forEach((key) => { + // ondrag event + if (mouse[ctx].dragging[key] && mouse[ctx].dragging[key].drag) + mouse.listen[ctx][key].ondrag.emit({ + target: evn.target, + px: mouse[ctx].prev.x, + py: mouse[ctx].prev.y, + x: mouse[ctx].pos.x, + y: mouse[ctx].pos.y, + timestamp: new Date(), + }); - // onpaint event - if (mouse[ctx].dragging[key]) - mouse.listen[ctx][key].onpaint.emit({ - target: evn.target, - px: mouse[ctx].prev.x, - py: mouse[ctx].prev.y, - x: mouse[ctx].pos.x, - y: mouse[ctx].pos.y, - timestamp: new Date(), - }); - }); - }); + // onpaint event + if (mouse[ctx].dragging[key]) + mouse.listen[ctx][key].onpaint.emit({ + target: evn.target, + px: mouse[ctx].prev.x, + py: mouse[ctx].prev.y, + x: mouse[ctx].pos.x, + y: mouse[ctx].pos.y, + timestamp: new Date(), + }); + }); + }); }; /** MOUSE DEBUG */ /* From 93b611825420f8cd87c61e80a6034ff791620863 Mon Sep 17 00:00:00 2001 From: Victor Seiji Hariki Date: Mon, 21 Nov 2022 08:24:35 -0300 Subject: [PATCH 21/22] some simple features to inputjs and menu overhaul for dragging, add original target to avoid paint intermitence when dragging windows too fast. Signed-off-by: Victor Seiji Hariki --- js/index.js | 4 ++-- js/input.js | 6 +++++- js/settingsbar.js | 48 ++++++++++++++++++++++++++++++++++++++++------- 3 files changed, 48 insertions(+), 10 deletions(-) diff --git a/js/index.js b/js/index.js index c939f30..bf8ebc1 100644 --- a/js/index.js +++ b/js/index.js @@ -461,7 +461,7 @@ mouse.listen.canvas.onmousemove.on((evn) => { }); mouse.listen.canvas.left.onpaint.on((evn) => { - if (paintMode && evn.target.id === "overlayCanvas") { + if (paintMode && evn.initialTarget.id === "overlayCanvas") { maskPaintCtx.globalCompositeOperation = "source-over"; maskPaintCtx.strokeStyle = "#FF6A6A"; @@ -475,7 +475,7 @@ mouse.listen.canvas.left.onpaint.on((evn) => { }); mouse.listen.canvas.right.onpaint.on((evn) => { - if (paintMode && evn.target.id === "overlayCanvas") { + if (paintMode && evn.initialTarget.id === "overlayCanvas") { maskPaintCtx.globalCompositeOperation = "destination-out"; maskPaintCtx.strokeStyle = "#FFFFFFFF"; diff --git a/js/input.js b/js/input.js index 6a76c2e..432b93b 100644 --- a/js/input.js +++ b/js/input.js @@ -130,7 +130,7 @@ window.onmousedown = (evn) => { ["window", "canvas", "world"].forEach((ctx) => { mouse.buttons[key] = time; - mouse[ctx].dragging[key] = {}; + mouse[ctx].dragging[key] = {target: evn.target}; Object.assign(mouse[ctx].dragging[key], mouse[ctx].pos); // onpaintstart event @@ -181,6 +181,7 @@ window.onmouseup = (evn) => { // onpaintend event mouse.listen[ctx][key].onpaintend.emit({ target: evn.target, + initialTarget: mouse[ctx].dragging[key].target, buttonId: evn.button, x: mouse[ctx].pos.x, y: mouse[ctx].pos.y, @@ -191,6 +192,7 @@ window.onmouseup = (evn) => { if (mouse[ctx].dragging[key].drag) mouse.listen[ctx][key].ondragend.emit({ target: evn.target, + initialTarget: mouse[ctx].dragging[key].target, buttonId: evn.button, x: mouse[ctx].pos.x, y: mouse[ctx].pos.y, @@ -244,6 +246,7 @@ window.onmousemove = (evn) => { if (mouse[ctx].dragging[key] && mouse[ctx].dragging[key].drag) mouse.listen[ctx][key].ondrag.emit({ target: evn.target, + initialTarget: mouse[ctx].dragging[key].target, px: mouse[ctx].prev.x, py: mouse[ctx].prev.y, x: mouse[ctx].pos.x, @@ -255,6 +258,7 @@ window.onmousemove = (evn) => { if (mouse[ctx].dragging[key]) mouse.listen[ctx][key].onpaint.emit({ target: evn.target, + initialTarget: mouse[ctx].dragging[key].target, px: mouse[ctx].prev.x, py: mouse[ctx].prev.y, x: mouse[ctx].pos.x, diff --git a/js/settingsbar.js b/js/settingsbar.js index 84120a3..6a4aec6 100644 --- a/js/settingsbar.js +++ b/js/settingsbar.js @@ -1,11 +1,10 @@ -dragElement(document.getElementById("infoContainer")); +//dragElement(document.getElementById("infoContainer")); +//dragElement(document.getElementById("historyContainer")); function dragElement(elmnt) { - var p1 = 0, - p2 = 0, - p3 = 0, + var p3 = 0, p4 = 0; - var draggableElements = document.getElementsByClassName("draggable"); + var draggableElements = elmnt.getElementsByClassName("draggable"); for (var i = 0; i < draggableElements.length; i++) { draggableElements[i].onmousedown = dragMouseDown; } @@ -20,8 +19,8 @@ function dragElement(elmnt) { function elementDrag(e) { e.preventDefault(); - p1 = p3 - e.clientX; - p2 = p4 - e.clientY; + elmnt.style.bottom = null; + elmnt.style.right = null; elmnt.style.top = elmnt.offsetTop - (p4 - e.clientY) + "px"; elmnt.style.left = elmnt.offsetLeft - (p3 - e.clientX) + "px"; p3 = e.clientX; @@ -34,6 +33,41 @@ function dragElement(elmnt) { } } +function makeDraggable(id) { + const element = document.getElementById(id); + const startbb = element.getBoundingClientRect(); + let dragging = false; + let offset = {x: 0, y: 0}; + + element.style.top = startbb.y + "px"; + element.style.left = startbb.x + "px"; + + mouse.listen.window.left.onpaintstart.on((evn) => { + if ( + element.contains(evn.target) && + evn.target.classList.contains("draggable") + ) { + const bb = element.getBoundingClientRect(); + offset.x = evn.x - bb.x; + offset.y = evn.y - bb.y; + dragging = true; + } + }); + + mouse.listen.window.left.onpaint.on((evn) => { + if (dragging) { + element.style.top = evn.y - offset.y + "px"; + element.style.left = evn.x - offset.x + "px"; + } + }); + + mouse.listen.window.left.onpaintend.on((evn) => { + dragging = false; + }); +} + +makeDraggable("infoContainer"); + var coll = document.getElementsByClassName("collapsible"); for (var i = 0; i < coll.length; i++) { coll[i].addEventListener("click", function () { From c1bc54a4ebea3c1e376cc75762c0bbde7fae1644 Mon Sep 17 00:00:00 2001 From: Victor Seiji Hariki Date: Mon, 21 Nov 2022 08:27:21 -0300 Subject: [PATCH 22/22] add new history window and fix multi-window support Signed-off-by: Victor Seiji Hariki --- css/index.css | 219 +++++++++++++++++++++++----------------------- index.html | 16 +++- js/settingsbar.js | 1 + 3 files changed, 126 insertions(+), 110 deletions(-) diff --git a/css/index.css b/css/index.css index 6f23d25..bd5b3d8 100644 --- a/css/index.css +++ b/css/index.css @@ -1,188 +1,191 @@ * { - font-size: 100%; - font-family: Arial, Helvetica, sans-serif; + font-size: 100%; + font-family: Arial, Helvetica, sans-serif; } .container { - position: relative; + position: relative; } .backgroundCanvas { - background-color: #ccc; + background-color: #ccc; } .maskPaintCanvas { - border: 3px dotted #993355c0; + border: 3px dotted #993355c0; } .overlayCanvas { - border: 1px solid #f00; + border: 1px solid #f00; } .tempCanvas { - border: 3px dotted #007affc0; + border: 3px dotted #007affc0; } .targetCanvas { - border: 2px dashed #0f0; + border: 2px dashed #0f0; } .canvas { - border: 2px dotted #00f; + border: 2px dotted #00f; } .mainHSplit { - display: grid; - grid-template-columns: 1fr; - grid-template-rows: repeat(2, 1fr); - grid-column-gap: 5px; - grid-row-gap: 5px; + display: grid; + grid-template-columns: 1fr; + grid-template-rows: repeat(2, 1fr); + grid-column-gap: 5px; + grid-row-gap: 5px; } .uiWrapper { - display: grid; - grid-template-columns: 1fr 15fr; - grid-template-rows: 1fr; - grid-column-gap: 5px; - grid-row-gap: 5px; + display: grid; + grid-template-columns: 1fr 15fr; + grid-template-rows: 1fr; + grid-column-gap: 5px; + grid-row-gap: 5px; } -#infoContainer { - position: absolute; - width: 250px; - height: auto; - z-index: 999; -} -#draggable { - cursor: move; +.uiContainer { + position: absolute; + width: 250px; + height: auto; + z-index: 999; } -#DraggableTitleBar { - z-index: 999; - cursor: move; - background-color: rgba(104, 104, 104, 0.75); +.uiTitleBar { + z-index: 999; + cursor: move; + background-color: rgba(104, 104, 104, 0.75); - padding-left: 5px; - padding-right: 5px; - padding-top: 5px; - padding-bottom: 5px; - margin-bottom: auto; - font-size: 1.5em; - color: black; - text-align: center; - border-top-left-radius: 10px; - border-top-right-radius: 10px; - border: solid; - border-bottom: none; - border-color: black; + user-select: none; + + padding-left: 5px; + padding-right: 5px; + padding-top: 5px; + padding-bottom: 5px; + margin-bottom: auto; + font-size: 1.5em; + color: black; + text-align: center; + border-top-left-radius: 10px; + border-top-right-radius: 10px; + border: solid; + border-bottom: none; + border-color: black; +} + +.draggable { + cursor: move; } .toolbar { - display: flex; - justify-content: space-between; + display: flex; + justify-content: space-between; } .toolbar > .tool { - flex: 1; + flex: 1; } .toolbar > .tool:not(:last-child) { - margin-right: 10px; + margin-right: 10px; } button.tool { - background-color: rgb(0, 0, 50); - color: rgb(255, 255, 255); - border-radius: 5px; - cursor: pointer; - border: none; - text-align: center; - outline: none; - font-size: 15px; - padding: 5px; - margin-top: 5px; - margin-bottom: 5px; + background-color: rgb(0, 0, 50); + color: rgb(255, 255, 255); + border-radius: 5px; + cursor: pointer; + border: none; + text-align: center; + outline: none; + font-size: 15px; + padding: 5px; + margin-top: 5px; + margin-bottom: 5px; } button.tool:hover { - background-color: #667; + background-color: #667; } .collapsible { - background-color: rgb(0, 0, 0); - color: rgb(255, 255, 255); - border-radius: 5px; - cursor: pointer; - width: 100%; - border: none; - text-align: center; - outline: none; - font-size: 15px; - padding: 5px; - margin-top: 5px; - margin-bottom: 5px; + background-color: rgb(0, 0, 0); + color: rgb(255, 255, 255); + border-radius: 5px; + cursor: pointer; + width: 100%; + border: none; + text-align: center; + outline: none; + font-size: 15px; + padding: 5px; + margin-top: 5px; + margin-bottom: 5px; } .collapsible:hover { - background-color: #777; + background-color: #777; } .content { - max-height: 0; - overflow: hidden; - transition: max-height 0.2s ease-out; + max-height: 0; + overflow: hidden; + transition: max-height 0.2s ease-out; } .info { - background-color: rgba(255, 255, 255, 0.5); - padding-left: 10px; - padding-right: 10px; - padding-top: 5px; - padding-bottom: 5px; + background-color: rgba(255, 255, 255, 0.5); + padding-left: 10px; + padding-right: 10px; + padding-top: 5px; + padding-bottom: 5px; - color: black; - border: solid; - border-top: none; - border-color: black; - font-size: medium; - text-align: left; - max-height: fit-content; - overflow: auto; - cursor: auto; + color: black; + border: solid; + border-top: none; + border-color: black; + font-size: medium; + text-align: left; + max-height: fit-content; + overflow: auto; + cursor: auto; } .canvasHolder { - position: relative; - width: 2560px; - height: 1440px; + position: relative; + width: 2560px; + height: 1440px; } .mainCanvases { - position: absolute; - top: 0px; - left: 0px; - width: 2560px; - height: 1440px; + position: absolute; + top: 0px; + left: 0px; + width: 2560px; + height: 1440px; } .masks { - display: grid; - grid-template-columns: repeat(3, 1fr); - grid-template-rows: 1fr; - grid-column-gap: 0px; - grid-row-gap: 0px; + display: grid; + grid-template-columns: repeat(3, 1fr); + grid-template-rows: 1fr; + grid-column-gap: 0px; + grid-row-gap: 0px; } .maskCanvasMonitor .overMaskCanvasMonitor .initImgCanvasMonitor { - position: absolute; + position: absolute; } .maskPaintCanvas { - filter: opacity(40%); + filter: opacity(40%); } .strokeText { - -webkit-text-stroke: 1px #888; - font-size: 150%; - color: #000; + -webkit-text-stroke: 1px #888; + font-size: 150%; + color: #000; } diff --git a/index.html b/index.html index bbaa0ec..42f70b7 100644 --- a/index.html +++ b/index.html @@ -9,8 +9,9 @@ -
-
openOutpaint 🐠
+ +
+
openOutpaint 🐠
@@ -113,6 +114,17 @@

+
+ +
+ + +
+
History
+
+
+ +
diff --git a/js/settingsbar.js b/js/settingsbar.js index 6a4aec6..b7d6e42 100644 --- a/js/settingsbar.js +++ b/js/settingsbar.js @@ -67,6 +67,7 @@ function makeDraggable(id) { } makeDraggable("infoContainer"); +makeDraggable("historyContainer"); var coll = document.getElementsByClassName("collapsible"); for (var i = 0; i < coll.length; i++) {