Merge pull request #60 from zero01101/bleeding-edge
Automatic Layers and Not-Quite Infinity
This commit is contained in:
commit
3281277e4f
24 changed files with 1716 additions and 1117 deletions
|
@ -69,7 +69,7 @@ you'll obviously need A1111's webUI installed before you can use this, thus you'
|
||||||
|
|
||||||
5. configure your local webhost in your homelab to serve the newly cloned repo like the technological bastion you are, or simply run the included `openOutpaint.bat` on windows or `openOutpaint.sh` on mac/linux.
|
5. configure your local webhost in your homelab to serve the newly cloned repo like the technological bastion you are, or simply run the included `openOutpaint.bat` on windows or `openOutpaint.sh` on mac/linux.
|
||||||
6. open your locally-hosted web server at http://127.0.0.1:3456 (or wherever, i'm not your boss)
|
6. open your locally-hosted web server at http://127.0.0.1:3456 (or wherever, i'm not your boss)
|
||||||
7. update the host field if necessary to point at your stable diffusion API address, change my stupid prompts with whatever you want, click somewhere in the canvas using the dream [`d`] tool, and wait (**OR** you can load as many existing images from your computer as you'd like using the "stamp image" tool [`U`]). if you've requested a batch of generated images and one of them sparks you as something you might want to use later, you can click the "res" button to add the image to the stampable dream resources as well. _(NOTE: you can select or deselect imported images/added resources freely simply by clicking on them)_
|
7. update the host field if necessary to point at your stable diffusion API address, change my stupid prompts with whatever you want, click somewhere in the canvas using the dream [`d`] tool, and wait (**OR** you can load as many existing images from your computer as you'd like using the "stamp image" tool [`U`]). If you've requested a batch of generated images and one of them sparks you as something you might want to use later, you can click the "res" button to add the image to the stampable dream resources as well. _(NOTE: you can select or deselect imported images/added resources freely simply by clicking on them)_
|
||||||
8. once an image appears\*, click the `<` and `>` buttons at the bottom-left corner of the image to cycle through the others in the batch if you requested multiple (it defaults to 2 batch size, 2 batch count) - click `y` to choose one you like, or `n` to cancel that image generation batch outright and possibly try again
|
8. once an image appears\*, click the `<` and `>` buttons at the bottom-left corner of the image to cycle through the others in the batch if you requested multiple (it defaults to 2 batch size, 2 batch count) - click `y` to choose one you like, or `n` to cancel that image generation batch outright and possibly try again
|
||||||
9. now that you've got a starter, click somewhere near it to outpaint - try and include as much of the "context" as possible in the reticle for the best result convergence, or you can right-click to remove some of it if you want to completely retry a chunk but leave the rest alone
|
9. now that you've got a starter, click somewhere near it to outpaint - try and include as much of the "context" as possible in the reticle for the best result convergence, or you can right-click to remove some of it if you want to completely retry a chunk but leave the rest alone
|
||||||
10. enable the mask mode to prepare previously rendered imagery for touchups/inpainting, then paint over the objectionable region; once your masked region is drawn, disable mask mode and change your prompt if necessary, then click over the canvas containing the mask you just painted to request the refined image(s)
|
10. enable the mask mode to prepare previously rendered imagery for touchups/inpainting, then paint over the objectionable region; once your masked region is drawn, disable mask mode and change your prompt if necessary, then click over the canvas containing the mask you just painted to request the refined image(s)
|
||||||
|
|
|
@ -2,8 +2,16 @@
|
||||||
font-size: 100%;
|
font-size: 100%;
|
||||||
font-family: Arial, Helvetica, sans-serif;
|
font-family: Arial, Helvetica, sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Body is stuck with no scroll */
|
||||||
body {
|
body {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
margin: 0px;
|
margin: 0px;
|
||||||
|
padding: 0px;
|
||||||
|
|
||||||
|
overflow: clip;
|
||||||
}
|
}
|
||||||
|
|
||||||
.container {
|
.container {
|
||||||
|
@ -96,31 +104,27 @@ body {
|
||||||
grid-row-gap: 0px;
|
grid-row-gap: 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maskCanvasMonitor .overMaskCanvasMonitor .initImgCanvasMonitor {
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Mask colors for mask inversion */
|
/* Mask colors for mask inversion */
|
||||||
/* Filters are some magic acquired at https://codepen.io/sosuke/pen/Pjoqqp */
|
/* Filters are some magic acquired at https://codepen.io/sosuke/pen/Pjoqqp */
|
||||||
.maskPaintCanvas {
|
.mask-canvas {
|
||||||
opacity: 0%;
|
opacity: 0%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maskPaintCanvas.display {
|
.mask-canvas.display {
|
||||||
opacity: 40%;
|
opacity: 40%;
|
||||||
filter: invert(100%);
|
filter: invert(100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.maskPaintCanvas.display.opaque {
|
.mask-canvas.display.opaque {
|
||||||
opacity: 100%;
|
opacity: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.maskPaintCanvas.display.clear {
|
.mask-canvas.display.clear {
|
||||||
filter: invert(71%) sepia(46%) saturate(6615%) hue-rotate(321deg)
|
filter: invert(71%) sepia(46%) saturate(6615%) hue-rotate(321deg)
|
||||||
brightness(106%) contrast(100%);
|
brightness(106%) contrast(100%);
|
||||||
}
|
}
|
||||||
|
|
||||||
.maskPaintCanvas.display.hold {
|
.mask-canvas.display.hold {
|
||||||
filter: invert(41%) sepia(16%) saturate(5181%) hue-rotate(218deg)
|
filter: invert(41%) sepia(16%) saturate(5181%) hue-rotate(218deg)
|
||||||
brightness(103%) contrast(108%);
|
brightness(103%) contrast(108%);
|
||||||
}
|
}
|
||||||
|
@ -193,6 +197,10 @@ body {
|
||||||
background-color: #dddd49;
|
background-color: #dddd49;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.host-field-wrapper .connection-status.before {
|
||||||
|
background-color: #777;
|
||||||
|
}
|
||||||
|
|
||||||
input#host {
|
input#host {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
@ -207,11 +215,6 @@ div.prompt-wrapper > textarea {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
|
||||||
top: 0px;
|
|
||||||
bottom: 0px;
|
|
||||||
left: 0px;
|
|
||||||
right: 0;
|
|
||||||
|
|
||||||
resize: vertical;
|
resize: vertical;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,7 +223,6 @@ div.prompt-wrapper > textarea:focus {
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Tool buttons */
|
/* Tool buttons */
|
||||||
|
|
||||||
.button-array {
|
.button-array {
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: stretch;
|
justify-content: stretch;
|
||||||
|
|
46
css/layers.css
Normal file
46
css/layers.css
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
/* Debug floating window */
|
||||||
|
#layer-preview .preview-canvas {
|
||||||
|
background-color: white;
|
||||||
|
width: 100%;
|
||||||
|
height: 150px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#layer-manager .menu-container {
|
||||||
|
height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-render-target {
|
||||||
|
position: fixed;
|
||||||
|
background-color: #466;
|
||||||
|
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-render-target .collection {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-render-target .collection > .collection-input-overlay {
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-render-target canvas {
|
||||||
|
position: absolute;
|
||||||
|
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
}
|
|
@ -9,6 +9,10 @@
|
||||||
background-color: var(--c-primary);
|
background-color: var(--c-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#ui-toolbar * {
|
||||||
|
user-select: none;
|
||||||
|
}
|
||||||
|
|
||||||
#ui-toolbar .handle {
|
#ui-toolbar .handle {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
119
index.html
119
index.html
|
@ -7,6 +7,8 @@
|
||||||
<link href="css/colors.css" rel="stylesheet" />
|
<link href="css/colors.css" rel="stylesheet" />
|
||||||
|
|
||||||
<link href="css/index.css" rel="stylesheet" />
|
<link href="css/index.css" rel="stylesheet" />
|
||||||
|
<link href="css/layers.css" rel="stylesheet" />
|
||||||
|
|
||||||
<link href="css/ui/generic.css" rel="stylesheet" />
|
<link href="css/ui/generic.css" rel="stylesheet" />
|
||||||
|
|
||||||
<link href="css/ui/history.css" rel="stylesheet" />
|
<link href="css/ui/history.css" rel="stylesheet" />
|
||||||
|
@ -190,109 +192,26 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Canvases -->
|
<!-- Canvases -->
|
||||||
<div
|
<div id="layer-render" class="layer-render-target"></div>
|
||||||
id="mainHSplit"
|
|
||||||
class="mainHSplit"
|
|
||||||
onmouseover="document.activeElement.blur()">
|
|
||||||
<div id="uiWrapper" class="uiWrapper">
|
|
||||||
<div
|
|
||||||
id="canvasHolder"
|
|
||||||
class="canvasHolder"
|
|
||||||
oncontextmenu="return false;">
|
|
||||||
<canvas
|
|
||||||
id="backgroundCanvas"
|
|
||||||
class="mainCanvases backgroundCanvas"
|
|
||||||
width="2560"
|
|
||||||
height="1440"
|
|
||||||
style="z-index: 0">
|
|
||||||
<!-- gray grid bg canvas -->
|
|
||||||
<p>lol ur browser sucks</p>
|
|
||||||
</canvas>
|
|
||||||
<canvas
|
|
||||||
id="canvas"
|
|
||||||
class="mainCanvases canvas"
|
|
||||||
width="2560"
|
|
||||||
height="1440"
|
|
||||||
style="z-index: 1">
|
|
||||||
<!-- normal canvas on which images are drawn -->
|
|
||||||
<p>lol ur browser sucks</p>
|
|
||||||
</canvas>
|
|
||||||
<canvas
|
|
||||||
id="tempCanvas"
|
|
||||||
class="mainCanvases tempCanvas"
|
|
||||||
width="2560"
|
|
||||||
height="1440"
|
|
||||||
style="z-index: 2">
|
|
||||||
<!-- temporary canvas on which images being selected/rejected or imported arbitrary images are superimposed -->
|
|
||||||
<p>lol ur browser sucks</p>
|
|
||||||
</canvas>
|
|
||||||
<canvas
|
|
||||||
id="targetCanvas"
|
|
||||||
class="mainCanvases targetCanvas"
|
|
||||||
width="2560"
|
|
||||||
height="1440"
|
|
||||||
style="z-index: 3">
|
|
||||||
<!-- canvas on which "targeting" squares are drawn -->
|
|
||||||
<p>lol ur browser sucks</p>
|
|
||||||
</canvas>
|
|
||||||
<canvas
|
|
||||||
id="maskPaintCanvas"
|
|
||||||
class="mainCanvases maskPaintCanvas"
|
|
||||||
width="2560"
|
|
||||||
height="1440"
|
|
||||||
style="z-index: 4">
|
|
||||||
<!-- canvas on which masking brush is "painted" -->
|
|
||||||
<p>lol ur browser sucks</p>
|
|
||||||
</canvas>
|
|
||||||
<canvas
|
|
||||||
id="overlayCanvas"
|
|
||||||
class="mainCanvases overlayCanvas"
|
|
||||||
width="2560"
|
|
||||||
height="1440"
|
|
||||||
style="z-index: 5">
|
|
||||||
<!-- canvas on which "cursor" reticle or arc is drawn -->
|
|
||||||
<p>lol ur browser sucks</p>
|
|
||||||
</canvas>
|
|
||||||
<div id="tempDiv" style="position: relative; z-index: 6">
|
|
||||||
<!-- where popup buttons go -->
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div id="masks" class="masks">
|
|
||||||
<div>
|
|
||||||
<!-- <canvas id="maskCanvasMonitor" class="maskCanvasMonitor" width="512" height="512">
|
|
||||||
<p>lol ur browser sucks</p>
|
|
||||||
</canvas><br /> -->
|
|
||||||
<canvas
|
|
||||||
id="overMaskCanvasMonitor"
|
|
||||||
class="overMaskCanvasMonitor"
|
|
||||||
width="512"
|
|
||||||
height="512">
|
|
||||||
<p>lol ur browser sucks</p>
|
|
||||||
</canvas>
|
|
||||||
<br />
|
|
||||||
<canvas
|
|
||||||
id="initImgCanvasMonitor"
|
|
||||||
class="initImgCanvasMonitor"
|
|
||||||
width="512"
|
|
||||||
height="512">
|
|
||||||
<p>lol ur browser sucks</p>
|
|
||||||
</canvas>
|
|
||||||
<br />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Base Libs -->
|
<!-- Base Libs -->
|
||||||
<script src="js/util.js" type="text/javascript"></script>
|
<script src="js/lib/util.js" type="text/javascript"></script>
|
||||||
<script src="js/input.js" type="text/javascript"></script>
|
<script src="js/lib/input.js" type="text/javascript"></script>
|
||||||
<script src="js/commands.js" type="text/javascript"></script>
|
<script src="js/lib/layers.js" type="text/javascript"></script>
|
||||||
<script src="js/ui/history.js" type="text/javascript"></script>
|
<script src="js/lib/commands.js" type="text/javascript"></script>
|
||||||
|
|
||||||
<script src="js/settingsbar.js" type="text/javascript"></script>
|
<script src="js/settingsbar.js" type="text/javascript"></script>
|
||||||
|
|
||||||
|
<script src="js/lib/toolbar.js" type="text/javascript"></script>
|
||||||
|
|
||||||
|
<script
|
||||||
|
src="js/initalize/layers.populate.js"
|
||||||
|
type="text/javascript"></script>
|
||||||
|
|
||||||
<!-- Content -->
|
<!-- Content -->
|
||||||
<script src="js/index.js" type="text/javascript"></script>
|
<script src="js/index.js" type="text/javascript"></script>
|
||||||
<script src="js/shortcuts.js" type="text/javascript"></script>
|
<script src="js/shortcuts.js" type="text/javascript"></script>
|
||||||
|
<script src="js/ui/floating/history.js" type="text/javascript"></script>
|
||||||
|
|
||||||
<!-- Load Tools -->
|
<!-- Load Tools -->
|
||||||
<script src="js/ui/tool/dream.js" type="text/javascript"></script>
|
<script src="js/ui/tool/dream.js" type="text/javascript"></script>
|
||||||
|
@ -300,6 +219,12 @@
|
||||||
<script src="js/ui/tool/select.js" type="text/javascript"></script>
|
<script src="js/ui/tool/select.js" type="text/javascript"></script>
|
||||||
<script src="js/ui/tool/stamp.js" type="text/javascript"></script>
|
<script src="js/ui/tool/stamp.js" type="text/javascript"></script>
|
||||||
|
|
||||||
<script src="js/ui/toolbar.js" type="text/javascript"></script>
|
<!-- Initialize -->
|
||||||
|
<script
|
||||||
|
src="js/initalize/toolbar.populate.js"
|
||||||
|
type="text/javascript"></script>
|
||||||
|
<script
|
||||||
|
src="js/initalize/debug.populate.js"
|
||||||
|
type="text/javascript"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
374
js/index.js
374
js/index.js
|
@ -48,7 +48,6 @@ var stableDiffusionData = {
|
||||||
};
|
};
|
||||||
|
|
||||||
// stuff things use
|
// stuff things use
|
||||||
var blockNewImages = false;
|
|
||||||
var returnedImages;
|
var returnedImages;
|
||||||
var imageIndex = 0;
|
var imageIndex = 0;
|
||||||
var tmpImgXYWH = {};
|
var tmpImgXYWH = {};
|
||||||
|
@ -57,15 +56,6 @@ var url = "/sdapi/v1/";
|
||||||
var endpoint = "txt2img";
|
var endpoint = "txt2img";
|
||||||
var frameX = 512;
|
var frameX = 512;
|
||||||
var frameY = 512;
|
var frameY = 512;
|
||||||
var prevMouseX = 0;
|
|
||||||
var prevMouseY = 0;
|
|
||||||
var mouseX = 0;
|
|
||||||
var mouseY = 0;
|
|
||||||
var canvasX = 0;
|
|
||||||
var canvasY = 0;
|
|
||||||
var heldButton = 0;
|
|
||||||
var snapX = 0;
|
|
||||||
var snapY = 0;
|
|
||||||
var drawThis = {};
|
var drawThis = {};
|
||||||
const basePixelCount = 64; //64 px - ALWAYS 64 PX
|
const basePixelCount = 64; //64 px - ALWAYS 64 PX
|
||||||
var scaleFactor = 8; //x64 px
|
var scaleFactor = 8; //x64 px
|
||||||
|
@ -85,47 +75,25 @@ var arbitraryImageBitmap;
|
||||||
var arbitraryImageBase64; // seriously js cmon work with me here
|
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 placingArbitraryImage = false; // for when the user has loaded an existing image from their computer
|
||||||
var marchOffset = 0;
|
var marchOffset = 0;
|
||||||
var stopMarching = null;
|
|
||||||
var inProgress = false;
|
var inProgress = false;
|
||||||
var marchCoords = {};
|
var marchCoords = {};
|
||||||
|
|
||||||
// info div, sometimes hidden
|
//
|
||||||
let mouseXInfo = document.getElementById("mouseX");
|
|
||||||
let mouseYInfo = document.getElementById("mouseY");
|
|
||||||
let canvasXInfo = document.getElementById("canvasX");
|
|
||||||
let canvasYInfo = document.getElementById("canvasY");
|
|
||||||
let snapXInfo = document.getElementById("snapX");
|
|
||||||
let snapYInfo = document.getElementById("snapY");
|
|
||||||
let heldButtonInfo = document.getElementById("heldButton");
|
|
||||||
|
|
||||||
// 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");
|
|
||||||
|
|
||||||
function startup() {
|
function startup() {
|
||||||
testHostConfiguration();
|
testHostConfiguration();
|
||||||
testHostConnection();
|
|
||||||
|
|
||||||
loadSettings();
|
loadSettings();
|
||||||
|
|
||||||
const hostEl = document.getElementById("host");
|
const hostEl = document.getElementById("host");
|
||||||
|
testHostConnection().then((checkConnection) => {
|
||||||
hostEl.onchange = () => {
|
hostEl.onchange = () => {
|
||||||
host = hostEl.value.endsWith("/")
|
host = hostEl.value.endsWith("/")
|
||||||
? hostEl.value.substring(0, hostEl.value.length - 1)
|
? hostEl.value.substring(0, hostEl.value.length - 1)
|
||||||
: hostEl.value;
|
: hostEl.value;
|
||||||
hostEl.value = host;
|
hostEl.value = host;
|
||||||
localStorage.setItem("host", host);
|
localStorage.setItem("host", host);
|
||||||
|
checkConnection();
|
||||||
};
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const promptEl = document.getElementById("prompt");
|
const promptEl = document.getElementById("prompt");
|
||||||
promptEl.oninput = () => {
|
promptEl.oninput = () => {
|
||||||
|
@ -147,9 +115,6 @@ function startup() {
|
||||||
changeSeed();
|
changeSeed();
|
||||||
changeOverMaskPx();
|
changeOverMaskPx();
|
||||||
changeHiResFix();
|
changeHiResFix();
|
||||||
document.getElementById("overlayCanvas").onmousemove = mouseMove;
|
|
||||||
document.getElementById("overlayCanvas").onmousedown = mouseDown;
|
|
||||||
document.getElementById("overlayCanvas").onmouseup = mouseUp;
|
|
||||||
document.getElementById("scaleFactor").value = scaleFactor;
|
document.getElementById("scaleFactor").value = scaleFactor;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,6 +146,10 @@ function testHostConfiguration() {
|
||||||
"Host seems to be invalid! Please fix your host here:",
|
"Host seems to be invalid! Please fix your host here:",
|
||||||
current
|
current
|
||||||
);
|
);
|
||||||
|
else
|
||||||
|
host = current.endsWith("/")
|
||||||
|
? current.substring(0, current.length - 1)
|
||||||
|
: current;
|
||||||
} else {
|
} else {
|
||||||
requestHost(
|
requestHost(
|
||||||
"This seems to be the first time you are using openOutpaint! Please set your host here:"
|
"This seems to be the first time you are using openOutpaint! Please set your host here:"
|
||||||
|
@ -188,12 +157,8 @@ function testHostConfiguration() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function testHostConnection() {
|
async function testHostConnection() {
|
||||||
function CheckInProgressError(message = "") {
|
class CheckInProgressError extends Error {}
|
||||||
this.name = "CheckInProgressError";
|
|
||||||
this.message = message;
|
|
||||||
}
|
|
||||||
CheckInProgressError.prototype = Object.create(Error.prototype);
|
|
||||||
|
|
||||||
const connectionIndicator = document.getElementById(
|
const connectionIndicator = document.getElementById(
|
||||||
"connection-status-indicator"
|
"connection-status-indicator"
|
||||||
|
@ -209,6 +174,7 @@ function testHostConnection() {
|
||||||
connectionIndicator.classList.remove(
|
connectionIndicator.classList.remove(
|
||||||
"cors-issue",
|
"cors-issue",
|
||||||
"offline",
|
"offline",
|
||||||
|
"before",
|
||||||
"server-error"
|
"server-error"
|
||||||
);
|
);
|
||||||
connectionIndicator.title = "Connected";
|
connectionIndicator.title = "Connected";
|
||||||
|
@ -216,7 +182,12 @@ function testHostConnection() {
|
||||||
},
|
},
|
||||||
error: () => {
|
error: () => {
|
||||||
connectionIndicator.classList.add("server-error");
|
connectionIndicator.classList.add("server-error");
|
||||||
connectionIndicator.classList.remove("online", "offline", "cors-issue");
|
connectionIndicator.classList.remove(
|
||||||
|
"online",
|
||||||
|
"offline",
|
||||||
|
"before",
|
||||||
|
"cors-issue"
|
||||||
|
);
|
||||||
connectionIndicator.title =
|
connectionIndicator.title =
|
||||||
"Server is online, but is returning an error response";
|
"Server is online, but is returning an error response";
|
||||||
connectionStatus = false;
|
connectionStatus = false;
|
||||||
|
@ -226,6 +197,7 @@ function testHostConnection() {
|
||||||
connectionIndicator.classList.remove(
|
connectionIndicator.classList.remove(
|
||||||
"online",
|
"online",
|
||||||
"offline",
|
"offline",
|
||||||
|
"before",
|
||||||
"server-error"
|
"server-error"
|
||||||
);
|
);
|
||||||
connectionIndicator.title =
|
connectionIndicator.title =
|
||||||
|
@ -237,17 +209,31 @@ function testHostConnection() {
|
||||||
connectionIndicator.classList.remove(
|
connectionIndicator.classList.remove(
|
||||||
"cors-issue",
|
"cors-issue",
|
||||||
"online",
|
"online",
|
||||||
|
"before",
|
||||||
"server-error"
|
"server-error"
|
||||||
);
|
);
|
||||||
connectionIndicator.title =
|
connectionIndicator.title =
|
||||||
"Server seems to be offline. Please check the console for more information.";
|
"Server seems to be offline. Please check the console for more information.";
|
||||||
connectionStatus = false;
|
connectionStatus = false;
|
||||||
},
|
},
|
||||||
|
before: () => {
|
||||||
|
connectionIndicator.classList.add("before");
|
||||||
|
connectionIndicator.classList.remove(
|
||||||
|
"cors-issue",
|
||||||
|
"online",
|
||||||
|
"offline",
|
||||||
|
"server-error"
|
||||||
|
);
|
||||||
|
connectionIndicator.title = "Waiting for check to complete.";
|
||||||
|
connectionStatus = false;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
statuses[status] && statuses[status]();
|
statuses[status] && statuses[status]();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
setConnectionStatus("before");
|
||||||
|
|
||||||
let checkInProgress = false;
|
let checkInProgress = false;
|
||||||
|
|
||||||
const checkConnection = async (notify = false) => {
|
const checkConnection = async (notify = false) => {
|
||||||
|
@ -259,7 +245,10 @@ function testHostConnection() {
|
||||||
var url = document.getElementById("host").value + "/startup-events";
|
var url = document.getElementById("host").value + "/startup-events";
|
||||||
// Attempt normal request
|
// Attempt normal request
|
||||||
try {
|
try {
|
||||||
const response = await fetch(url);
|
/** @type {Response} */
|
||||||
|
const response = await fetch(url, {
|
||||||
|
signal: AbortSignal.timeout(5000),
|
||||||
|
});
|
||||||
|
|
||||||
if (response.status === 200) {
|
if (response.status === 200) {
|
||||||
setConnectionStatus("online");
|
setConnectionStatus("online");
|
||||||
|
@ -278,6 +267,7 @@ function testHostConnection() {
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
try {
|
try {
|
||||||
|
if (e instanceof DOMException) throw "offline";
|
||||||
// Tests if problem is CORS
|
// Tests if problem is CORS
|
||||||
await fetch(url, {mode: "no-cors"});
|
await fetch(url, {mode: "no-cors"});
|
||||||
|
|
||||||
|
@ -301,7 +291,7 @@ function testHostConnection() {
|
||||||
return status;
|
return status;
|
||||||
};
|
};
|
||||||
|
|
||||||
checkConnection(true);
|
await checkConnection(true);
|
||||||
|
|
||||||
// On click, attempt to refresh
|
// On click, attempt to refresh
|
||||||
connectionIndicator.onclick = async () => {
|
connectionIndicator.onclick = async () => {
|
||||||
|
@ -316,8 +306,8 @@ function testHostConnection() {
|
||||||
// Checks every 5 seconds if offline, 30 seconds if online
|
// Checks every 5 seconds if offline, 30 seconds if online
|
||||||
const checkAgain = () => {
|
const checkAgain = () => {
|
||||||
setTimeout(
|
setTimeout(
|
||||||
() => {
|
async () => {
|
||||||
checkConnection();
|
await checkConnection();
|
||||||
checkAgain();
|
checkAgain();
|
||||||
},
|
},
|
||||||
connectionStatus ? 30000 : 5000
|
connectionStatus ? 30000 : 5000
|
||||||
|
@ -325,133 +315,15 @@ function testHostConnection() {
|
||||||
};
|
};
|
||||||
|
|
||||||
checkAgain();
|
checkAgain();
|
||||||
}
|
|
||||||
|
|
||||||
function dream(
|
return () => {
|
||||||
x,
|
checkConnection().catch(() => {});
|
||||||
y,
|
|
||||||
prompt,
|
|
||||||
extra = {
|
|
||||||
method: endpoint,
|
|
||||||
stopMarching: () => {},
|
|
||||||
bb: {x, y, w: prompt.width, h: prompt.height},
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
tmpImgXYWH.x = x;
|
|
||||||
tmpImgXYWH.y = y;
|
|
||||||
tmpImgXYWH.w = prompt.width;
|
|
||||||
tmpImgXYWH.h = prompt.height;
|
|
||||||
console.log(
|
|
||||||
"dreaming to " +
|
|
||||||
host +
|
|
||||||
url +
|
|
||||||
(extra.method || endpoint) +
|
|
||||||
":\r\n" +
|
|
||||||
JSON.stringify(prompt)
|
|
||||||
);
|
|
||||||
console.info(`dreaming "${prompt.prompt}"`);
|
|
||||||
console.debug(prompt);
|
|
||||||
|
|
||||||
// Start checking for progress
|
|
||||||
const progressCheck = checkProgress(extra.bb);
|
|
||||||
postData(prompt, extra)
|
|
||||||
.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, extra);
|
|
||||||
})
|
|
||||||
.finally(() => clearInterval(progressCheck));
|
|
||||||
}
|
|
||||||
|
|
||||||
async function postData(promptData, extra = null) {
|
|
||||||
this.host = document.getElementById("host").value;
|
|
||||||
// Default options are marked with *
|
|
||||||
const response = await fetch(
|
|
||||||
this.host + this.url + extra.method || endpoint,
|
|
||||||
{
|
|
||||||
method: "POST", // *GET, POST, PUT, DELETE, etc.
|
|
||||||
mode: "cors", // no-cors, *cors, same-origin
|
|
||||||
cache: "default", // *default, no-cache, reload, force-cache, only-if-cached
|
|
||||||
credentials: "same-origin", // include, *same-origin, omit
|
|
||||||
headers: {
|
|
||||||
Accept: "application/json",
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
},
|
|
||||||
redirect: "follow", // manual, *follow, error
|
|
||||||
referrerPolicy: "no-referrer", // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
|
|
||||||
body: JSON.stringify(promptData), // body data type must match "Content-Type" header
|
|
||||||
}
|
|
||||||
);
|
|
||||||
return response.json(); // parses JSON response into native JavaScript objects
|
|
||||||
}
|
|
||||||
|
|
||||||
function imageAcceptReject(x, y, data, extra = null) {
|
|
||||||
inProgress = false;
|
|
||||||
document.getElementById("progressDiv").remove();
|
|
||||||
const img = new Image();
|
|
||||||
img.onload = function () {
|
|
||||||
backupAndClearMask(x, y, img.width, img.height);
|
|
||||||
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) < 0 ? 0 + "px" : parseInt(x) + "px";
|
|
||||||
div.style.top = parseInt(y + data.parameters.height) + "px";
|
|
||||||
div.style.width = "200px";
|
|
||||||
div.style.height = "70px";
|
|
||||||
div.innerHTML =
|
|
||||||
'<button onclick="prevImg(this)"><</button><button onclick="nextImg(this)">></button><span class="strokeText" id="currentImgIndex"></span><span class="strokeText"> of </span><span class="strokeText" id="totalImgIndex"></span><button onclick="accept(this)">Y</button><button onclick="reject(this)">N</button><button onclick="resource(this)">RES</button><span class="strokeText" id="estRemaining"></span>';
|
|
||||||
|
|
||||||
document.getElementById("tempDiv").appendChild(div);
|
|
||||||
document.getElementById("currentImgIndex").innerText = "1";
|
|
||||||
document.getElementById("totalImgIndex").innerText = totalImagesReturned;
|
|
||||||
};
|
};
|
||||||
// set the image displayed as the first regardless of batch size/count
|
|
||||||
imageIndex = 0;
|
|
||||||
// load the image data after defining the closure
|
|
||||||
img.src = "data:image/png;base64," + returnedImages[imageIndex];
|
|
||||||
}
|
|
||||||
|
|
||||||
function accept(evt) {
|
|
||||||
// write image to imgcanvas
|
|
||||||
stopMarching && stopMarching();
|
|
||||||
stopMarching = null;
|
|
||||||
clearBackupMask();
|
|
||||||
placeImage();
|
|
||||||
removeChoiceButtons();
|
|
||||||
clearTargetMask();
|
|
||||||
blockNewImages = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function reject(evt) {
|
|
||||||
// remove image entirely
|
|
||||||
stopMarching && stopMarching();
|
|
||||||
stopMarching = null;
|
|
||||||
restoreBackupMask();
|
|
||||||
clearBackupMask();
|
|
||||||
clearTargetMask();
|
|
||||||
removeChoiceButtons();
|
|
||||||
blockNewImages = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resource(evt) {
|
|
||||||
// send image to resources
|
|
||||||
const img = new Image();
|
|
||||||
// load the image data after defining the closure
|
|
||||||
img.src = "data:image/png;base64," + returnedImages[imageIndex];
|
|
||||||
|
|
||||||
tools.stamp.state.addResource(
|
|
||||||
prompt("Enter new resource name", "Dream Resource"),
|
|
||||||
img
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function newImage(evt) {
|
function newImage(evt) {
|
||||||
clearPaintedMask();
|
clearPaintedMask();
|
||||||
clearBackupMask();
|
clearBackupMask();
|
||||||
clearTargetMask();
|
|
||||||
commands.runCommand("eraseImage", "Clear Canvas", {
|
commands.runCommand("eraseImage", "Clear Canvas", {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
|
@ -532,10 +404,6 @@ function clearBackupMask() {
|
||||||
backupMaskY = null;
|
backupMaskY = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
function clearTargetMask() {
|
|
||||||
tgtCtx.clearRect(0, 0, tgtCanvas.width, tgtCanvas.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearImgMask() {
|
function clearImgMask() {
|
||||||
imgCtx.clearRect(0, 0, imgCanvas.width, imgCanvas.height);
|
imgCtx.clearRect(0, 0, imgCanvas.width, imgCanvas.height);
|
||||||
}
|
}
|
||||||
|
@ -565,122 +433,37 @@ function sleep(ms) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function march(bb) {
|
function march(bb) {
|
||||||
|
const expanded = {...bb};
|
||||||
|
expanded.x--;
|
||||||
|
expanded.y--;
|
||||||
|
expanded.w += 2;
|
||||||
|
expanded.h += 2;
|
||||||
|
|
||||||
|
// Get temporary layer to draw marching ants
|
||||||
|
const layer = imageCollection.registerLayer(null, {
|
||||||
|
bb: expanded,
|
||||||
|
});
|
||||||
|
layer.canvas.style.imageRendering = "pixelated";
|
||||||
let offset = 0;
|
let offset = 0;
|
||||||
|
|
||||||
const interval = setInterval(() => {
|
const interval = setInterval(() => {
|
||||||
drawMarchingAnts(bb, offset++);
|
drawMarchingAnts(layer.ctx, bb, offset++);
|
||||||
offset %= 16;
|
offset %= 12;
|
||||||
}, 20);
|
}, 20);
|
||||||
|
|
||||||
return () => clearInterval(interval);
|
return () => {
|
||||||
|
clearInterval(interval);
|
||||||
|
imageCollection.deleteLayer(layer);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawMarchingAnts(bb, offset) {
|
function drawMarchingAnts(ctx, bb, offset) {
|
||||||
clearTargetMask();
|
ctx.clearRect(0, 0, bb.w + 2, bb.h + 2);
|
||||||
tgtCtx.strokeStyle = "#FFFFFFFF"; //"#55000077";
|
ctx.strokeStyle = "#FFFFFFFF"; //"#55000077";
|
||||||
tgtCtx.setLineDash([4, 2]);
|
ctx.strokeWidth = "2px";
|
||||||
tgtCtx.lineDashOffset = -offset;
|
ctx.setLineDash([4, 2]);
|
||||||
tgtCtx.strokeRect(bb.x, bb.y, bb.w, bb.h);
|
ctx.lineDashOffset = -offset;
|
||||||
}
|
ctx.strokeRect(1, 1, bb.w, bb.h);
|
||||||
|
|
||||||
function checkProgress(bb) {
|
|
||||||
document.getElementById("progressDiv") &&
|
|
||||||
document.getElementById("progressDiv").remove();
|
|
||||||
// Skip image to stop using a ton of networking resources
|
|
||||||
endpoint = "progress?skip_current_image=true";
|
|
||||||
var div = document.createElement("div");
|
|
||||||
div.id = "progressDiv";
|
|
||||||
div.style.position = "absolute";
|
|
||||||
div.style.width = "200px";
|
|
||||||
div.style.height = "70px";
|
|
||||||
div.style.left = parseInt(bb.x + bb.w - 100) + "px";
|
|
||||||
div.style.top = parseInt(bb.y + bb.h) + "px";
|
|
||||||
div.innerHTML = '<span class="strokeText" id="estRemaining"></span>';
|
|
||||||
document.getElementById("tempDiv").appendChild(div);
|
|
||||||
return setInterval(() => {
|
|
||||||
fetch(host + url + endpoint)
|
|
||||||
.then((response) => response.json())
|
|
||||||
.then((data) => {
|
|
||||||
var estimate =
|
|
||||||
Math.round(data.progress * 100) +
|
|
||||||
"% :: " +
|
|
||||||
Math.floor(data.eta_relative) +
|
|
||||||
" sec.";
|
|
||||||
|
|
||||||
document.getElementById("estRemaining").innerText = estimate;
|
|
||||||
});
|
|
||||||
}, 1000);
|
|
||||||
}
|
|
||||||
|
|
||||||
function mouseMove(evt) {
|
|
||||||
const rect = ovCanvas.getBoundingClientRect(); // not-quite pixel offset was driving me insane
|
|
||||||
const canvasOffsetX = rect.left;
|
|
||||||
const canvasOffsetY = rect.top;
|
|
||||||
heldButton = evt.buttons;
|
|
||||||
mouseXInfo.innerText = mouseX = evt.clientX;
|
|
||||||
mouseYInfo.innerText = mouseY = evt.clientY;
|
|
||||||
canvasXInfo.innerText = canvasX = parseInt(evt.clientX - rect.left);
|
|
||||||
canvasYInfo.innerText = canvasY = parseInt(evt.clientY - rect.top);
|
|
||||||
snapXInfo.innerText = canvasX + snap(canvasX);
|
|
||||||
snapYInfo.innerText = canvasY + snap(canvasY);
|
|
||||||
heldButtonInfo.innerText = heldButton;
|
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); // clear out the previous mouse cursor
|
|
||||||
if (placingArbitraryImage) {
|
|
||||||
// ugh refactor so this isn't duplicated between arbitrary image and dream reticle modes
|
|
||||||
snapOffsetX = 0;
|
|
||||||
snapOffsetY = 0;
|
|
||||||
if (snapToGrid) {
|
|
||||||
snapOffsetX = snap(canvasX, false);
|
|
||||||
snapOffsetY = snap(canvasY, false);
|
|
||||||
}
|
|
||||||
finalX = snapOffsetX + canvasX;
|
|
||||||
finalY = snapOffsetY + canvasY;
|
|
||||||
ovCtx.drawImage(arbitraryImage, finalX, finalY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mouseDown(evt) {
|
|
||||||
const rect = ovCanvas.getBoundingClientRect();
|
|
||||||
var oddOffset = 0;
|
|
||||||
if (scaleFactor % 2 != 0) {
|
|
||||||
oddOffset = basePixelCount / 2;
|
|
||||||
}
|
|
||||||
if (evt.button == 0) {
|
|
||||||
// left click
|
|
||||||
if (placingArbitraryImage) {
|
|
||||||
var nextBox = {};
|
|
||||||
nextBox.x = evt.offsetX;
|
|
||||||
nextBox.y = evt.offsetY;
|
|
||||||
nextBox.w = arbitraryImageData.width;
|
|
||||||
nextBox.h = arbitraryImageData.height;
|
|
||||||
dropTargets.push(nextBox);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function mouseUp(evt) {
|
|
||||||
if (evt.button == 0) {
|
|
||||||
// left click
|
|
||||||
if (placingArbitraryImage) {
|
|
||||||
// jeez i REALLY need to refactor tons of this to not be duplicated all over, that's definitely my next chore after figuring out that razza frazza overmask fade
|
|
||||||
var target = dropTargets[dropTargets.length - 1]; //get the last one... why am i storing all of them?
|
|
||||||
snapOffsetX = 0;
|
|
||||||
snapOffsetY = 0;
|
|
||||||
if (snapToGrid) {
|
|
||||||
snapOffsetX = snap(target.x, false);
|
|
||||||
snapOffsetY = snap(target.y, false);
|
|
||||||
}
|
|
||||||
finalX = snapOffsetX + target.x;
|
|
||||||
finalY = snapOffsetY + target.y;
|
|
||||||
|
|
||||||
drawThis.x = finalX;
|
|
||||||
drawThis.y = finalY;
|
|
||||||
drawThis.w = target.w;
|
|
||||||
drawThis.h = target.h;
|
|
||||||
drawIt = drawThis; // i still think this is really stupid and redundant and unnecessary and redundant
|
|
||||||
drop(drawIt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function changeSampler() {
|
function changeSampler() {
|
||||||
|
@ -799,10 +582,11 @@ function drawBackground() {
|
||||||
// Checkerboard
|
// Checkerboard
|
||||||
let darkTileColor = "#333";
|
let darkTileColor = "#333";
|
||||||
let lightTileColor = "#555";
|
let lightTileColor = "#555";
|
||||||
for (var x = 0; x < bgCanvas.width; x += 64) {
|
for (var x = 0; x < bgLayer.canvas.width; x += 64) {
|
||||||
for (var y = 0; y < bgCanvas.height; y += 64) {
|
for (var y = 0; y < bgLayer.canvas.height; y += 64) {
|
||||||
bgCtx.fillStyle = (x + y) % 128 === 0 ? lightTileColor : darkTileColor;
|
bgLayer.ctx.fillStyle =
|
||||||
bgCtx.fillRect(x, y, 64, 64);
|
(x + y) % 128 === 0 ? lightTileColor : darkTileColor;
|
||||||
|
bgLayer.ctx.fillRect(x, y, 64, 64);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1081,6 +865,18 @@ function loadSettings() {
|
||||||
// document.getElementById("overMaskPx").value = Number(_overmask_px);
|
// document.getElementById("overMaskPx").value = Number(_overmask_px);
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById("mainHSplit").addEventListener("wheel", (evn) => {
|
imageCollection.element.addEventListener(
|
||||||
|
"wheel",
|
||||||
|
(evn) => {
|
||||||
evn.preventDefault();
|
evn.preventDefault();
|
||||||
});
|
},
|
||||||
|
{passive: false}
|
||||||
|
);
|
||||||
|
|
||||||
|
imageCollection.element.addEventListener(
|
||||||
|
"contextmenu",
|
||||||
|
(evn) => {
|
||||||
|
evn.preventDefault();
|
||||||
|
},
|
||||||
|
{passive: false}
|
||||||
|
);
|
||||||
|
|
29
js/initalize/debug.populate.js
Normal file
29
js/initalize/debug.populate.js
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
// 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");
|
||||||
|
|
||||||
|
mouse.listen.window.onmousemove.on((evn) => {
|
||||||
|
mouseXInfo.textContent = evn.x;
|
||||||
|
mouseYInfo.textContent = evn.y;
|
||||||
|
});
|
||||||
|
|
||||||
|
mouse.listen.world.onmousemove.on((evn) => {
|
||||||
|
canvasXInfo.textContent = evn.x;
|
||||||
|
canvasYInfo.textContent = evn.y;
|
||||||
|
snapXInfo.textContent = evn.x + snap(evn.x);
|
||||||
|
snapYInfo.textContent = evn.y + snap(evn.y);
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Toggles the debug layer (Just run toggledebug() in the console)
|
||||||
|
*/
|
||||||
|
const toggledebug = () => {
|
||||||
|
const hidden = debugCanvas.style.display === "none";
|
||||||
|
if (hidden) debugLayer.unhide();
|
||||||
|
else debugLayer.hide();
|
||||||
|
};
|
189
js/initalize/layers.populate.js
Normal file
189
js/initalize/layers.populate.js
Normal file
|
@ -0,0 +1,189 @@
|
||||||
|
// Layering
|
||||||
|
const imageCollection = layers.registerCollection(
|
||||||
|
"image",
|
||||||
|
{w: 2560, h: 1536},
|
||||||
|
{
|
||||||
|
name: "Image Layers",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const bgLayer = imageCollection.registerLayer("bg", {
|
||||||
|
name: "Background",
|
||||||
|
});
|
||||||
|
const imgLayer = imageCollection.registerLayer("image", {
|
||||||
|
name: "Image",
|
||||||
|
});
|
||||||
|
const maskPaintLayer = imageCollection.registerLayer("mask", {
|
||||||
|
name: "Mask Paint",
|
||||||
|
});
|
||||||
|
const ovLayer = imageCollection.registerLayer("overlay", {
|
||||||
|
name: "Overlay",
|
||||||
|
});
|
||||||
|
const debugLayer = imageCollection.registerLayer("debug", {
|
||||||
|
name: "Debug Layer",
|
||||||
|
});
|
||||||
|
|
||||||
|
const imgCanvas = imgLayer.canvas; // where dreams go
|
||||||
|
const imgCtx = imgLayer.ctx;
|
||||||
|
|
||||||
|
const maskPaintCanvas = maskPaintLayer.canvas; // where mouse cursor renders
|
||||||
|
const maskPaintCtx = maskPaintLayer.ctx;
|
||||||
|
|
||||||
|
maskPaintCanvas.classList.add("mask-canvas");
|
||||||
|
|
||||||
|
const ovCanvas = ovLayer.canvas; // where mouse cursor renders
|
||||||
|
const ovCtx = ovLayer.ctx;
|
||||||
|
|
||||||
|
const debugCanvas = debugLayer.canvas; // where mouse cursor renders
|
||||||
|
const debugCtx = debugLayer.ctx;
|
||||||
|
|
||||||
|
debugLayer.hide(); // Hidden by default
|
||||||
|
|
||||||
|
layers.registerCollection("mask", {name: "Mask Layers", requiresActive: true});
|
||||||
|
|
||||||
|
// Where CSS and javascript magic happens to make the canvas viewport work
|
||||||
|
/**
|
||||||
|
* Ended up using a CSS transforms approach due to more flexibility on transformations
|
||||||
|
* and capability to automagically translate input coordinates to layer space.
|
||||||
|
*/
|
||||||
|
mouse.registerContext(
|
||||||
|
"world",
|
||||||
|
(evn, ctx) => {
|
||||||
|
// Fix because in chrome layerX and layerY simply doesnt work
|
||||||
|
/** @type {HTMLDivElement} */
|
||||||
|
const target = evn.target;
|
||||||
|
|
||||||
|
// Get element bounding rect
|
||||||
|
const bb = target.getBoundingClientRect();
|
||||||
|
|
||||||
|
// Get element width/height (css, cause I don't trust client sizes in chrome anymore)
|
||||||
|
const w = imageCollection.size.w;
|
||||||
|
const h = imageCollection.size.h;
|
||||||
|
|
||||||
|
// Get cursor position
|
||||||
|
const x = evn.clientX;
|
||||||
|
const y = evn.clientY;
|
||||||
|
|
||||||
|
// Map to layer space
|
||||||
|
const layerX = ((x - bb.left) / bb.width) * w;
|
||||||
|
const layerY = ((y - bb.top) / bb.height) * h;
|
||||||
|
|
||||||
|
//
|
||||||
|
ctx.coords.prev.x = ctx.coords.pos.x;
|
||||||
|
ctx.coords.prev.y = ctx.coords.pos.y;
|
||||||
|
ctx.coords.pos.x = layerX;
|
||||||
|
ctx.coords.pos.y = layerY;
|
||||||
|
},
|
||||||
|
{target: imageCollection.inputElement}
|
||||||
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The global viewport object (may be modularized in the future). All
|
||||||
|
* coordinates given are of the center of the viewport
|
||||||
|
*
|
||||||
|
* cx and cy are the viewport's world coordinates, scaled to zoom level.
|
||||||
|
* _x and _y are actual coordinates in the DOM space
|
||||||
|
*
|
||||||
|
* The transform() function does some transforms and writes them to the
|
||||||
|
* provided element.
|
||||||
|
*/
|
||||||
|
const viewport = {
|
||||||
|
get cx() {
|
||||||
|
return this._x * this.zoom;
|
||||||
|
},
|
||||||
|
|
||||||
|
set cx(v) {
|
||||||
|
return (this._x = v / this.zoom);
|
||||||
|
},
|
||||||
|
_x: 0,
|
||||||
|
get cy() {
|
||||||
|
return this._y * this.zoom;
|
||||||
|
},
|
||||||
|
set cy(v) {
|
||||||
|
return (this._y = v / this.zoom);
|
||||||
|
},
|
||||||
|
_y: 0,
|
||||||
|
zoom: 1,
|
||||||
|
rotation: 0,
|
||||||
|
get w() {
|
||||||
|
return (window.innerWidth * 1) / this.zoom;
|
||||||
|
},
|
||||||
|
get h() {
|
||||||
|
return (window.innerHeight * 1) / this.zoom;
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Apply transformation
|
||||||
|
*
|
||||||
|
* @param {HTMLElement} el Element to apply CSS transform to
|
||||||
|
*/
|
||||||
|
transform(el) {
|
||||||
|
el.style.transformOrigin = `${this.cx}px ${this.cy}px`;
|
||||||
|
el.style.transform = `scale(${this.zoom}) translate(${-(
|
||||||
|
this._x -
|
||||||
|
this.w / 2
|
||||||
|
)}px, ${-(this._y - this.h / 2)}px)`;
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
viewport.cx = imageCollection.size.w / 2;
|
||||||
|
viewport.cy = imageCollection.size.h / 2;
|
||||||
|
|
||||||
|
let worldInit = null;
|
||||||
|
|
||||||
|
viewport.transform(imageCollection.element);
|
||||||
|
|
||||||
|
mouse.listen.window.onwheel.on((evn) => {
|
||||||
|
if (evn.evn.ctrlKey) {
|
||||||
|
evn.evn.preventDefault();
|
||||||
|
const pcx = viewport.cx;
|
||||||
|
const pcy = viewport.cy;
|
||||||
|
if (evn.delta < 0) {
|
||||||
|
viewport.zoom *= 1 + Math.abs(evn.delta * 0.0002);
|
||||||
|
} else {
|
||||||
|
viewport.zoom *= 1 - Math.abs(evn.delta * 0.0002);
|
||||||
|
}
|
||||||
|
|
||||||
|
viewport.cx = pcx;
|
||||||
|
viewport.cy = pcy;
|
||||||
|
|
||||||
|
viewport.transform(imageCollection.element);
|
||||||
|
|
||||||
|
debugCtx.clearRect(0, 0, debugCanvas.width, debugCanvas.height);
|
||||||
|
debugCtx.fillStyle = "#F0F";
|
||||||
|
debugCtx.beginPath();
|
||||||
|
debugCtx.arc(viewport.cx, viewport.cy, 5, 0, Math.PI * 2);
|
||||||
|
debugCtx.fill();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
mouse.listen.window.btn.middle.onpaintstart.on((evn) => {
|
||||||
|
worldInit = {x: viewport.cx, y: viewport.cy};
|
||||||
|
});
|
||||||
|
|
||||||
|
mouse.listen.window.btn.middle.onpaint.on((evn) => {
|
||||||
|
if (worldInit) {
|
||||||
|
viewport.cx = worldInit.x + (evn.ix - evn.x) / viewport.zoom;
|
||||||
|
viewport.cy = worldInit.y + (evn.iy - evn.y) / viewport.zoom;
|
||||||
|
|
||||||
|
// Limits
|
||||||
|
viewport.cx = Math.max(Math.min(viewport.cx, imageCollection.size.w), 0);
|
||||||
|
viewport.cy = Math.max(Math.min(viewport.cy, imageCollection.size.h), 0);
|
||||||
|
|
||||||
|
// Draw Viewport location
|
||||||
|
}
|
||||||
|
|
||||||
|
viewport.transform(imageCollection.element);
|
||||||
|
debugCtx.clearRect(0, 0, debugCanvas.width, debugCanvas.height);
|
||||||
|
debugCtx.fillStyle = "#F0F";
|
||||||
|
debugCtx.beginPath();
|
||||||
|
debugCtx.arc(viewport.cx, viewport.cy, 5, 0, Math.PI * 2);
|
||||||
|
debugCtx.fill();
|
||||||
|
});
|
||||||
|
|
||||||
|
mouse.listen.window.btn.middle.onpaintend.on((evn) => {
|
||||||
|
worldInit = null;
|
||||||
|
});
|
||||||
|
|
||||||
|
window.addEventListener("resize", () => {
|
||||||
|
viewport.transform(imageCollection.element);
|
||||||
|
});
|
27
js/initalize/toolbar.populate.js
Normal file
27
js/initalize/toolbar.populate.js
Normal file
|
@ -0,0 +1,27 @@
|
||||||
|
const tools = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dream tool
|
||||||
|
*/
|
||||||
|
tools.dream = dreamTool();
|
||||||
|
tools.img2img = img2imgTool();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mask Editing tools
|
||||||
|
*/
|
||||||
|
toolbar.addSeparator();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mask Brush tool
|
||||||
|
*/
|
||||||
|
tools.maskbrush = maskBrushTool();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Image Editing tools
|
||||||
|
*/
|
||||||
|
toolbar.addSeparator();
|
||||||
|
|
||||||
|
tools.selecttransform = selectTransformTool();
|
||||||
|
tools.stamp = stampTool();
|
||||||
|
|
||||||
|
toolbar.tools[0].enable();
|
|
@ -44,7 +44,7 @@
|
||||||
* @typedef MouseListenerContext
|
* @typedef MouseListenerContext
|
||||||
* @property {Observer} onmousemove A mouse move handler
|
* @property {Observer} onmousemove A mouse move handler
|
||||||
* @property {Observer} onwheel A mouse wheel handler
|
* @property {Observer} onwheel A mouse wheel handler
|
||||||
* @property {MouseListenerBtnContext} btn Button handlers
|
* @property {Record<string, MouseListenerBtnContext>} btn Button handlers
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -65,7 +65,10 @@
|
||||||
* @property {string} id A unique identifier
|
* @property {string} id A unique identifier
|
||||||
* @property {string} name The key name
|
* @property {string} name The key name
|
||||||
* @property {ContextMoveTransformer} onmove The coordinate transform callback
|
* @property {ContextMoveTransformer} onmove The coordinate transform callback
|
||||||
|
* @property {(evn) => void} onany A function to be run on any event
|
||||||
* @property {?HTMLElement} target The target
|
* @property {?HTMLElement} target The target
|
||||||
|
* @property {MouseCoordContext} coords Coordinates object
|
||||||
|
* @property {MouseListenerContext} listen Listeners object
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -64,6 +64,7 @@ const mouse = {
|
||||||
* @param {object} options Extra options
|
* @param {object} options Extra options
|
||||||
* @param {HTMLElement} [options.target=null] Target filtering
|
* @param {HTMLElement} [options.target=null] Target filtering
|
||||||
* @param {Record<number, string>} [options.buttons={0: "left", 1: "middle", 2: "right"}] Custom button mapping
|
* @param {Record<number, string>} [options.buttons={0: "left", 1: "middle", 2: "right"}] Custom button mapping
|
||||||
|
* @param {(evn) => void} [options.genericcb=null] Function that will be run for all events (useful for preventDefault)
|
||||||
* @returns {MouseContext}
|
* @returns {MouseContext}
|
||||||
*/
|
*/
|
||||||
registerContext: (name, onmove, options = {}) => {
|
registerContext: (name, onmove, options = {}) => {
|
||||||
|
@ -71,6 +72,7 @@ const mouse = {
|
||||||
defaultOpt(options, {
|
defaultOpt(options, {
|
||||||
target: null,
|
target: null,
|
||||||
buttons: {0: "left", 1: "middle", 2: "right"},
|
buttons: {0: "left", 1: "middle", 2: "right"},
|
||||||
|
genericcb: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
// Context information
|
// Context information
|
||||||
|
@ -79,6 +81,7 @@ const mouse = {
|
||||||
id: guid(),
|
id: guid(),
|
||||||
name,
|
name,
|
||||||
onmove,
|
onmove,
|
||||||
|
onany: options.genericcb,
|
||||||
target: options.target,
|
target: options.target,
|
||||||
buttons: options.buttons,
|
buttons: options.buttons,
|
||||||
};
|
};
|
||||||
|
@ -128,7 +131,9 @@ const mouse = {
|
||||||
const _double_click_timeout = {};
|
const _double_click_timeout = {};
|
||||||
const _drag_start_timeout = {};
|
const _drag_start_timeout = {};
|
||||||
|
|
||||||
window.onmousedown = (evn) => {
|
window.addEventListener(
|
||||||
|
"mousedown",
|
||||||
|
(evn) => {
|
||||||
const time = performance.now();
|
const time = performance.now();
|
||||||
|
|
||||||
if (_double_click_timeout[evn.button]) {
|
if (_double_click_timeout[evn.button]) {
|
||||||
|
@ -178,9 +183,11 @@ window.onmousedown = (evn) => {
|
||||||
|
|
||||||
mouse.buttons[evn.button] = time;
|
mouse.buttons[evn.button] = time;
|
||||||
|
|
||||||
mouse._contexts.forEach(({target, name, buttons}) => {
|
mouse._contexts.forEach(({target, name, buttons, onany}) => {
|
||||||
const key = buttons[evn.button];
|
const key = buttons[evn.button];
|
||||||
if ((!target || target === evn.target) && key) {
|
if ((!target || target === evn.target) && key) {
|
||||||
|
onany && onany();
|
||||||
|
|
||||||
mouse.coords[name].dragging[key] = {};
|
mouse.coords[name].dragging[key] = {};
|
||||||
mouse.coords[name].dragging[key].target = evn.target;
|
mouse.coords[name].dragging[key].target = evn.target;
|
||||||
Object.assign(mouse.coords[name].dragging[key], mouse.coords[name].pos);
|
Object.assign(mouse.coords[name].dragging[key], mouse.coords[name].pos);
|
||||||
|
@ -196,18 +203,25 @@ window.onmousedown = (evn) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
},
|
||||||
|
{
|
||||||
|
passive: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
window.onmouseup = (evn) => {
|
window.addEventListener(
|
||||||
|
"mouseup",
|
||||||
|
(evn) => {
|
||||||
const time = performance.now();
|
const time = performance.now();
|
||||||
|
|
||||||
mouse._contexts.forEach(({target, name, buttons}) => {
|
mouse._contexts.forEach(({target, name, buttons, onany}) => {
|
||||||
const key = buttons[evn.button];
|
const key = buttons[evn.button];
|
||||||
if (
|
if (
|
||||||
(!target || target === evn.target) &&
|
(!target || target === evn.target) &&
|
||||||
key &&
|
key &&
|
||||||
mouse.coords[name].dragging[key]
|
mouse.coords[name].dragging[key]
|
||||||
) {
|
) {
|
||||||
|
onany && onany();
|
||||||
const start = {
|
const start = {
|
||||||
x: mouse.coords[name].dragging[key].x,
|
x: mouse.coords[name].dragging[key].x,
|
||||||
y: mouse.coords[name].dragging[key].y,
|
y: mouse.coords[name].dragging[key].y,
|
||||||
|
@ -267,9 +281,13 @@ window.onmouseup = (evn) => {
|
||||||
delete _drag_start_timeout[evn.button];
|
delete _drag_start_timeout[evn.button];
|
||||||
}
|
}
|
||||||
mouse.buttons[evn.button] = null;
|
mouse.buttons[evn.button] = null;
|
||||||
};
|
},
|
||||||
|
{passive: false}
|
||||||
|
);
|
||||||
|
|
||||||
window.onmousemove = (evn) => {
|
window.addEventListener(
|
||||||
|
"mousemove",
|
||||||
|
(evn) => {
|
||||||
mouse._contexts.forEach((context) => {
|
mouse._contexts.forEach((context) => {
|
||||||
const target = context.target;
|
const target = context.target;
|
||||||
const name = context.name;
|
const name = context.name;
|
||||||
|
@ -353,7 +371,9 @@ window.onmousemove = (evn) => {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
},
|
||||||
|
{passive: false}
|
||||||
|
);
|
||||||
|
|
||||||
window.addEventListener(
|
window.addEventListener(
|
||||||
"wheel",
|
"wheel",
|
||||||
|
@ -382,17 +402,6 @@ mouse.registerContext("window", (evn, ctx) => {
|
||||||
ctx.coords.pos.x = evn.clientX;
|
ctx.coords.pos.x = evn.clientX;
|
||||||
ctx.coords.pos.y = evn.clientY;
|
ctx.coords.pos.y = evn.clientY;
|
||||||
});
|
});
|
||||||
|
|
||||||
mouse.registerContext(
|
|
||||||
"canvas",
|
|
||||||
(evn, ctx) => {
|
|
||||||
ctx.coords.prev.x = ctx.coords.pos.x;
|
|
||||||
ctx.coords.prev.y = ctx.coords.pos.y;
|
|
||||||
ctx.coords.pos.x = evn.layerX;
|
|
||||||
ctx.coords.pos.y = evn.layerY;
|
|
||||||
},
|
|
||||||
document.getElementById("overlayCanvas")
|
|
||||||
);
|
|
||||||
/**
|
/**
|
||||||
* Keyboard input processing
|
* Keyboard input processing
|
||||||
*/
|
*/
|
264
js/lib/layers.js
Normal file
264
js/lib/layers.js
Normal file
|
@ -0,0 +1,264 @@
|
||||||
|
/**
|
||||||
|
* This is a manager for the many canvas and content layers that compose the application
|
||||||
|
*
|
||||||
|
* It manages canvases and their locations and sizes according to current viewport views
|
||||||
|
*/
|
||||||
|
const layers = {
|
||||||
|
_collections: [],
|
||||||
|
collections: makeWriteOnce({}, "layers.collections"),
|
||||||
|
|
||||||
|
listen: {
|
||||||
|
oncollectioncreate: new Observer(),
|
||||||
|
oncollectiondelete: new Observer(),
|
||||||
|
|
||||||
|
onlayercreate: new Observer(),
|
||||||
|
onlayerdelete: new Observer(),
|
||||||
|
},
|
||||||
|
|
||||||
|
// Registers a new collection
|
||||||
|
// Layer collections are a group of layers (canvases) that are rendered in tandem. (same width, height, position, transform, etc)
|
||||||
|
registerCollection: (key, size, options = {}) => {
|
||||||
|
defaultOpt(options, {
|
||||||
|
// Display name for the collection
|
||||||
|
name: key,
|
||||||
|
|
||||||
|
// Initial layer
|
||||||
|
initLayer: {
|
||||||
|
key: "default",
|
||||||
|
options: {},
|
||||||
|
},
|
||||||
|
|
||||||
|
// Target
|
||||||
|
targetElement: document.getElementById("layer-render"),
|
||||||
|
|
||||||
|
// Resolution of the image
|
||||||
|
resolution: size,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Path used for logging purposes
|
||||||
|
const _logpath = "layers.collections." + key;
|
||||||
|
|
||||||
|
// Collection ID
|
||||||
|
const id = guid();
|
||||||
|
|
||||||
|
// Collection element
|
||||||
|
const element = document.createElement("div");
|
||||||
|
element.id = `collection-${id}`;
|
||||||
|
element.style.width = `${size.w}px`;
|
||||||
|
element.style.height = `${size.h}px`;
|
||||||
|
element.classList.add("collection");
|
||||||
|
|
||||||
|
// Input element (overlay element for input handling)
|
||||||
|
const inputel = document.createElement("div");
|
||||||
|
inputel.id = `collection-input-${id}`;
|
||||||
|
inputel.style.width = `${size.w}px`;
|
||||||
|
inputel.style.height = `${size.h}px`;
|
||||||
|
inputel.addEventListener("mouseover", (evn) => {
|
||||||
|
document.activeElement.blur();
|
||||||
|
});
|
||||||
|
inputel.classList.add("collection-input-overlay");
|
||||||
|
element.appendChild(inputel);
|
||||||
|
|
||||||
|
options.targetElement.appendChild(element);
|
||||||
|
|
||||||
|
const collection = makeWriteOnce(
|
||||||
|
{
|
||||||
|
id,
|
||||||
|
|
||||||
|
_logpath,
|
||||||
|
|
||||||
|
_layers: [],
|
||||||
|
layers: {},
|
||||||
|
|
||||||
|
name: options.name,
|
||||||
|
element,
|
||||||
|
inputElement: inputel,
|
||||||
|
|
||||||
|
size,
|
||||||
|
resolution: options.resolution,
|
||||||
|
|
||||||
|
active: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a new layer
|
||||||
|
*
|
||||||
|
* @param {string | null} key Name and key to use to access layer. If null, it is a temporary layer.
|
||||||
|
* @param {object} options
|
||||||
|
* @param {string} options.name
|
||||||
|
* @param {?BoundingBox} options.bb
|
||||||
|
* @param {{w: number, h: number}} options.resolution
|
||||||
|
* @param {object} options.after
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
registerLayer: (key = null, options = {}) => {
|
||||||
|
// Make ID
|
||||||
|
const id = guid();
|
||||||
|
|
||||||
|
defaultOpt(options, {
|
||||||
|
// Display name for the layer
|
||||||
|
name: key || `Temporary ${id}`,
|
||||||
|
|
||||||
|
// Bounding box for layer
|
||||||
|
bb: {x: 0, y: 0, w: collection.size.w, h: collection.size.h},
|
||||||
|
|
||||||
|
// Bounding box for layer
|
||||||
|
resolution: null,
|
||||||
|
|
||||||
|
// If set, will insert the layer after the given one
|
||||||
|
after: null,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Calculate resolution
|
||||||
|
if (!options.resolution)
|
||||||
|
options.resolution = {
|
||||||
|
w: (collection.resolution.w / collection.size.w) * options.bb.w,
|
||||||
|
h: (collection.resolution.h / collection.size.h) * options.bb.h,
|
||||||
|
};
|
||||||
|
|
||||||
|
// This layer's canvas
|
||||||
|
// This is where black magic will take place in the future
|
||||||
|
/**
|
||||||
|
* @todo Use the canvas black arts to auto-scale canvas
|
||||||
|
*/
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.id = `layer-${id}`;
|
||||||
|
|
||||||
|
canvas.style.left = `${options.bb.x}px`;
|
||||||
|
canvas.style.top = `${options.bb.y}px`;
|
||||||
|
canvas.style.width = `${options.bb.w}px`;
|
||||||
|
canvas.style.height = `${options.bb.h}px`;
|
||||||
|
canvas.width = options.resolution.w;
|
||||||
|
canvas.height = options.resolution.h;
|
||||||
|
|
||||||
|
if (!options.after) collection.element.appendChild(canvas);
|
||||||
|
else {
|
||||||
|
options.after.canvas.after(canvas);
|
||||||
|
}
|
||||||
|
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
|
// Path used for logging purposes
|
||||||
|
const _layerlogpath = key
|
||||||
|
? _logpath + ".layers." + key
|
||||||
|
: _logpath + ".layers[" + id + "]";
|
||||||
|
const layer = makeWriteOnce(
|
||||||
|
{
|
||||||
|
_logpath: _layerlogpath,
|
||||||
|
_collection: collection,
|
||||||
|
|
||||||
|
id,
|
||||||
|
key,
|
||||||
|
name: options.name,
|
||||||
|
|
||||||
|
state: new Proxy(
|
||||||
|
{visible: true},
|
||||||
|
{
|
||||||
|
set(obj, opt, val) {
|
||||||
|
switch (opt) {
|
||||||
|
case "visible":
|
||||||
|
layer.canvas.style.display = val ? "block" : "none";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
obj[opt] = val;
|
||||||
|
},
|
||||||
|
}
|
||||||
|
),
|
||||||
|
|
||||||
|
/** Our canvas */
|
||||||
|
canvas,
|
||||||
|
ctx,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves this layer to another location
|
||||||
|
*
|
||||||
|
* @param {number} x X coordinate of the top left of the canvas
|
||||||
|
* @param {number} y X coordinate of the top left of the canvas
|
||||||
|
*/
|
||||||
|
moveTo(x, y) {
|
||||||
|
canvas.style.left = `${x}px`;
|
||||||
|
canvas.style.top = `${y}px`;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Hides this layer (don't draw)
|
||||||
|
hide() {
|
||||||
|
this.canvas.style.display = "none";
|
||||||
|
},
|
||||||
|
// Hides this layer (don't draw)
|
||||||
|
unhide() {
|
||||||
|
this.canvas.style.display = "block";
|
||||||
|
},
|
||||||
|
|
||||||
|
// Activates this layer
|
||||||
|
activate() {
|
||||||
|
collection.active = this;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_layerlogpath,
|
||||||
|
["active"]
|
||||||
|
);
|
||||||
|
|
||||||
|
// Add to indexers
|
||||||
|
if (!options.after) collection._layers.push(layer);
|
||||||
|
else {
|
||||||
|
const index = collection._layers.findIndex(
|
||||||
|
(l) => l === options.after
|
||||||
|
);
|
||||||
|
collection._layers.splice(index, 0, layer);
|
||||||
|
}
|
||||||
|
if (key) collection.layers[key] = layer;
|
||||||
|
|
||||||
|
if (key === null)
|
||||||
|
console.debug(
|
||||||
|
`[layers] Anonymous layer '${layer.name}' registered`
|
||||||
|
);
|
||||||
|
else
|
||||||
|
console.info(
|
||||||
|
`[layers] Layer '${layer.name}' at ${layer._logpath} registered`
|
||||||
|
);
|
||||||
|
|
||||||
|
layers.listen.onlayercreate.emit({
|
||||||
|
layer,
|
||||||
|
});
|
||||||
|
return layer;
|
||||||
|
},
|
||||||
|
|
||||||
|
// Deletes a layer
|
||||||
|
deleteLayer: (layer) => {
|
||||||
|
const lobj = collection._layers.splice(
|
||||||
|
collection._layers.findIndex(
|
||||||
|
(l) => l.id === layer || l.id === layer.id
|
||||||
|
),
|
||||||
|
1
|
||||||
|
)[0];
|
||||||
|
if (!lobj) return;
|
||||||
|
|
||||||
|
layers.listen.onlayerdelete.emit({
|
||||||
|
layer: lobj,
|
||||||
|
});
|
||||||
|
if (lobj.key) delete collection.layers[lobj.key];
|
||||||
|
|
||||||
|
collection.element.removeChild(lobj.canvas);
|
||||||
|
|
||||||
|
if (lobj.key) console.info(`[layers] Layer '${lobj.key}' deleted`);
|
||||||
|
else console.debug(`[layers] Anonymous layer '${lobj.id}' deleted`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
_logpath,
|
||||||
|
["active"]
|
||||||
|
);
|
||||||
|
|
||||||
|
layers._collections.push(collection);
|
||||||
|
layers.collections[key] = collection;
|
||||||
|
|
||||||
|
console.info(
|
||||||
|
`[layers] Collection '${options.name}' at ${_logpath} registered`
|
||||||
|
);
|
||||||
|
|
||||||
|
// We must create a layer to select
|
||||||
|
collection
|
||||||
|
.registerLayer(options.initLayer.key, options.initLayer.options)
|
||||||
|
.activate();
|
||||||
|
|
||||||
|
return collection;
|
||||||
|
},
|
||||||
|
};
|
|
@ -177,7 +177,6 @@ const _toolbar_input = {
|
||||||
* Dream and img2img tools
|
* Dream and img2img tools
|
||||||
*/
|
*/
|
||||||
const _reticle_draw = (evn, snapToGrid = true) => {
|
const _reticle_draw = (evn, snapToGrid = true) => {
|
||||||
if (evn.target.id === "overlayCanvas") {
|
|
||||||
const bb = getBoundingBox(
|
const bb = getBoundingBox(
|
||||||
evn.x,
|
evn.x,
|
||||||
evn.y,
|
evn.y,
|
||||||
|
@ -190,33 +189,4 @@ const _reticle_draw = (evn, snapToGrid = true) => {
|
||||||
ovCtx.lineWidth = 1;
|
ovCtx.lineWidth = 1;
|
||||||
ovCtx.strokeStyle = "#FFF";
|
ovCtx.strokeStyle = "#FFF";
|
||||||
ovCtx.strokeRect(bb.x, bb.y, bb.w, bb.h); //origin is middle of the frame
|
ovCtx.strokeRect(bb.x, bb.y, bb.w, bb.h); //origin is middle of the frame
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const tools = {};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Dream tool
|
|
||||||
*/
|
|
||||||
tools.dream = dreamTool();
|
|
||||||
tools.img2img = img2imgTool();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mask Editing tools
|
|
||||||
*/
|
|
||||||
toolbar.addSeparator();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Mask Brush tool
|
|
||||||
*/
|
|
||||||
tools.maskbrush = maskBrushTool();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Image Editing tools
|
|
||||||
*/
|
|
||||||
toolbar.addSeparator();
|
|
||||||
|
|
||||||
tools.selecttransform = selectTransformTool();
|
|
||||||
tools.stamp = stampTool();
|
|
||||||
|
|
||||||
toolbar.tools[0].enable();
|
|
|
@ -1,10 +1,21 @@
|
||||||
function makeDraggable(element) {
|
function makeDraggable(element) {
|
||||||
const startbb = element.getBoundingClientRect();
|
|
||||||
let dragging = false;
|
let dragging = false;
|
||||||
let offset = {x: 0, y: 0};
|
let offset = {x: 0, y: 0};
|
||||||
|
|
||||||
element.style.top = startbb.y + "px";
|
const margin = 10;
|
||||||
element.style.left = startbb.x + "px";
|
|
||||||
|
const fixPos = () => {
|
||||||
|
const dbb = element.getBoundingClientRect();
|
||||||
|
if (dbb.left < margin) element.style.left = margin + "px";
|
||||||
|
else if (dbb.right > window.innerWidth - margin)
|
||||||
|
element.style.left =
|
||||||
|
dbb.left + (window.innerWidth - margin - dbb.right) + "px";
|
||||||
|
|
||||||
|
if (dbb.top < margin) element.style.top = margin + "px";
|
||||||
|
else if (dbb.bottom > window.innerHeight - margin)
|
||||||
|
element.style.top =
|
||||||
|
dbb.top + (window.innerHeight - margin - dbb.bottom) + "px";
|
||||||
|
};
|
||||||
|
|
||||||
mouse.listen.window.btn.left.onpaintstart.on((evn) => {
|
mouse.listen.window.btn.left.onpaintstart.on((evn) => {
|
||||||
if (
|
if (
|
||||||
|
@ -20,14 +31,22 @@ function makeDraggable(element) {
|
||||||
|
|
||||||
mouse.listen.window.btn.left.onpaint.on((evn) => {
|
mouse.listen.window.btn.left.onpaint.on((evn) => {
|
||||||
if (dragging) {
|
if (dragging) {
|
||||||
|
element.style.right = null;
|
||||||
|
element.style.bottom = null;
|
||||||
element.style.top = evn.y - offset.y + "px";
|
element.style.top = evn.y - offset.y + "px";
|
||||||
element.style.left = evn.x - offset.x + "px";
|
element.style.left = evn.x - offset.x + "px";
|
||||||
|
|
||||||
|
fixPos();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
mouse.listen.window.btn.left.onpaintend.on((evn) => {
|
mouse.listen.window.btn.left.onpaintend.on((evn) => {
|
||||||
dragging = false;
|
dragging = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
window.addEventListener("resize", () => {
|
||||||
|
fixPos();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
document.querySelectorAll(".floating-window").forEach((w) => {
|
document.querySelectorAll(".floating-window").forEach((w) => {
|
||||||
|
@ -150,7 +169,7 @@ function createSlider(name, wrapper, options = {}) {
|
||||||
});
|
});
|
||||||
|
|
||||||
mouse.listen.window.btn.left.ondrag.on((evn) => {
|
mouse.listen.window.btn.left.ondrag.on((evn) => {
|
||||||
if (evn.target === overEl) {
|
if (evn.initialTarget === overEl) {
|
||||||
setValue(
|
setValue(
|
||||||
Math.max(
|
Math.max(
|
||||||
options.min,
|
options.min,
|
||||||
|
|
3
js/ui/explore.js
Normal file
3
js/ui/explore.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/**
|
||||||
|
* This is a simple implementation of layer interaction
|
||||||
|
*/
|
23
js/ui/tool/dream.d.js
Normal file
23
js/ui/tool/dream.d.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
/**
|
||||||
|
* Stable Diffusion Request
|
||||||
|
*
|
||||||
|
* @typedef StableDiffusionRequest
|
||||||
|
* @property {string} prompt Stable Diffusion prompt
|
||||||
|
* @property {string} negative_prompt Stable Diffusion negative prompt
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stable Diffusion Response
|
||||||
|
*
|
||||||
|
* @typedef StableDiffusionResponse
|
||||||
|
* @property {string[]} images Response images
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stable Diffusion Progress Response
|
||||||
|
*
|
||||||
|
* @typedef StableDiffusionProgressResponse
|
||||||
|
* @property {number} progress Progress (from 0 to 1)
|
||||||
|
* @property {number} eta_relative Estimated finish time
|
||||||
|
* @property {?string} current_image Progress image
|
||||||
|
*/
|
|
@ -1,5 +1,318 @@
|
||||||
const dream_generate_callback = (evn, state) => {
|
let blockNewImages = false;
|
||||||
if (evn.target.id === "overlayCanvas" && !blockNewImages) {
|
|
||||||
|
/**
|
||||||
|
* Starts progress monitoring bar
|
||||||
|
*
|
||||||
|
* @param {BoundingBox} bb Bouding Box to draw progress to
|
||||||
|
* @returns {() => void}
|
||||||
|
*/
|
||||||
|
const _monitorProgress = (bb) => {
|
||||||
|
const minDelay = 1000;
|
||||||
|
|
||||||
|
const apiURL = `${host}${url}progress?skip_current_image=true`;
|
||||||
|
|
||||||
|
const expanded = {...bb};
|
||||||
|
expanded.x--;
|
||||||
|
expanded.y--;
|
||||||
|
expanded.w += 2;
|
||||||
|
expanded.h += 2;
|
||||||
|
|
||||||
|
// Get temporary layer to draw progress bar
|
||||||
|
const layer = imageCollection.registerLayer(null, {
|
||||||
|
bb: expanded,
|
||||||
|
});
|
||||||
|
layer.canvas.style.opacity = "70%";
|
||||||
|
|
||||||
|
let running = true;
|
||||||
|
|
||||||
|
const _checkProgress = async () => {
|
||||||
|
const init = performance.now();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(apiURL);
|
||||||
|
/** @type {StableDiffusionProgressResponse} */
|
||||||
|
const data = await response.json();
|
||||||
|
|
||||||
|
// Draw Progress Bar
|
||||||
|
layer.ctx.fillStyle = "#5F5";
|
||||||
|
layer.ctx.fillRect(1, 1, bb.w * data.progress, 10);
|
||||||
|
|
||||||
|
// Draw Progress Text
|
||||||
|
layer.ctx.clearRect(0, 11, expanded.w, 40);
|
||||||
|
layer.ctx.fillStyle = "#FFF";
|
||||||
|
|
||||||
|
layer.ctx.fillRect(0, 15, 60, 25);
|
||||||
|
layer.ctx.fillRect(bb.w - 58, 15, 60, 25);
|
||||||
|
|
||||||
|
layer.ctx.font = "20px Open Sans";
|
||||||
|
layer.ctx.fillStyle = "#000";
|
||||||
|
layer.ctx.textAlign = "right";
|
||||||
|
layer.ctx.fillText(`${Math.round(data.progress * 100)}%`, 55, 35);
|
||||||
|
|
||||||
|
// Draw ETA Text
|
||||||
|
layer.ctx.fillText(`${Math.round(data.eta_relative)}s`, bb.w - 5, 35);
|
||||||
|
} finally {
|
||||||
|
}
|
||||||
|
|
||||||
|
const timeSpent = performance.now() - init;
|
||||||
|
setTimeout(() => {
|
||||||
|
if (running) _checkProgress();
|
||||||
|
}, Math.max(0, minDelay - timeSpent));
|
||||||
|
};
|
||||||
|
|
||||||
|
_checkProgress();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
imageCollection.deleteLayer(layer);
|
||||||
|
running = false;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Starts a dream
|
||||||
|
*
|
||||||
|
* @param {"txt2img" | "img2img"} endpoint Endpoint to send the request to
|
||||||
|
* @param {StableDiffusionRequest} request Stable diffusion request
|
||||||
|
* @returns {Promise<string[]>}
|
||||||
|
*/
|
||||||
|
const _dream = async (endpoint, request) => {
|
||||||
|
const apiURL = `${host}${url}${endpoint}`;
|
||||||
|
|
||||||
|
/** @type {StableDiffusionResponse} */
|
||||||
|
let data = null;
|
||||||
|
try {
|
||||||
|
const response = await fetch(apiURL, {
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
Accept: "application/json",
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
},
|
||||||
|
body: JSON.stringify(request),
|
||||||
|
});
|
||||||
|
|
||||||
|
data = await response.json();
|
||||||
|
} finally {
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.images;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate and pick an image for placement
|
||||||
|
*
|
||||||
|
* @param {"txt2img" | "img2img"} endpoint Endpoint to send the request to
|
||||||
|
* @param {StableDiffusionRequest} request Stable diffusion request
|
||||||
|
* @param {BoundingBox} bb Generated image placement location
|
||||||
|
* @returns {Promise<HTMLImageElement | null>}
|
||||||
|
*/
|
||||||
|
const _generate = async (endpoint, request, bb) => {
|
||||||
|
const requestCopy = {...request};
|
||||||
|
|
||||||
|
// Images to select through
|
||||||
|
let at = 0;
|
||||||
|
/** @type {Image[]} */
|
||||||
|
const images = [];
|
||||||
|
/** @type {HTMLDivElement} */
|
||||||
|
let imageSelectMenu = null;
|
||||||
|
|
||||||
|
// Layer for the images
|
||||||
|
const layer = imageCollection.registerLayer(null, {
|
||||||
|
after: maskPaintLayer,
|
||||||
|
});
|
||||||
|
|
||||||
|
const redraw = () => {
|
||||||
|
const image = new Image();
|
||||||
|
image.src = "data:image/png;base64," + images[at];
|
||||||
|
image.addEventListener("load", () => {
|
||||||
|
layer.ctx.clearRect(0, 0, layer.canvas.width, layer.canvas.height);
|
||||||
|
if (images[at]) layer.ctx.drawImage(image, bb.x, bb.y);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const stopMarchingAnts = march(bb);
|
||||||
|
|
||||||
|
// First Dream Run
|
||||||
|
let stopProgress = _monitorProgress(bb);
|
||||||
|
images.push(...(await _dream(endpoint, requestCopy)));
|
||||||
|
stopProgress();
|
||||||
|
|
||||||
|
// Image navigation
|
||||||
|
const prevImg = () => {
|
||||||
|
at--;
|
||||||
|
if (at < 0) at = images.length - 1;
|
||||||
|
|
||||||
|
imageindextxt.textContent = `${at + 1}/${images.length}`;
|
||||||
|
redraw();
|
||||||
|
};
|
||||||
|
|
||||||
|
const nextImg = () => {
|
||||||
|
at++;
|
||||||
|
if (at >= images.length) at = 0;
|
||||||
|
|
||||||
|
imageindextxt.textContent = `${at + 1}/${images.length}`;
|
||||||
|
redraw();
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyImg = async () => {
|
||||||
|
const img = new Image();
|
||||||
|
// load the image data after defining the closure
|
||||||
|
img.src = "data:image/png;base64," + images[at];
|
||||||
|
img.addEventListener("load", () => {
|
||||||
|
commands.runCommand("drawImage", "Image Dream", {
|
||||||
|
x: bb.x,
|
||||||
|
y: bb.y,
|
||||||
|
image: img,
|
||||||
|
});
|
||||||
|
clean(true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeMore = async () => {
|
||||||
|
let stopProgress = _monitorProgress(bb);
|
||||||
|
images.push(...(await _dream(endpoint, requestCopy)));
|
||||||
|
stopProgress();
|
||||||
|
|
||||||
|
imageindextxt.textContent = `${at + 1}/${images.length}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const discardImg = async () => {
|
||||||
|
clean();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Listen for keyboard arrows
|
||||||
|
const onarrow = (evn) => {
|
||||||
|
switch (evn.target.tagName.toLowerCase()) {
|
||||||
|
case "input":
|
||||||
|
case "textarea":
|
||||||
|
case "select":
|
||||||
|
case "button":
|
||||||
|
return; // If in an input field, do not process arrow input
|
||||||
|
default:
|
||||||
|
// Do nothing
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (evn.key) {
|
||||||
|
case "+":
|
||||||
|
makeMore();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
switch (evn.code) {
|
||||||
|
case "ArrowRight":
|
||||||
|
nextImg();
|
||||||
|
break;
|
||||||
|
case "ArrowLeft":
|
||||||
|
prevImg();
|
||||||
|
break;
|
||||||
|
case "Enter":
|
||||||
|
applyImg();
|
||||||
|
break;
|
||||||
|
case "Escape":
|
||||||
|
discardImg();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
keyboard.listen.onkeyclick.on(onarrow);
|
||||||
|
|
||||||
|
// Cleans up
|
||||||
|
const clean = (removeBrushMask = false) => {
|
||||||
|
if (removeBrushMask) {
|
||||||
|
maskPaintCtx.clearRect(bb.x, bb.y, bb.w, bb.h);
|
||||||
|
}
|
||||||
|
stopMarchingAnts();
|
||||||
|
imageCollection.inputElement.removeChild(imageSelectMenu);
|
||||||
|
imageCollection.deleteLayer(layer);
|
||||||
|
blockNewImages = false;
|
||||||
|
keyboard.listen.onkeyclick.clear(onarrow);
|
||||||
|
};
|
||||||
|
|
||||||
|
const makeElement = (type, x, y) => {
|
||||||
|
const el = document.createElement(type);
|
||||||
|
el.style.position = "absolute";
|
||||||
|
el.style.left = `${x}px`;
|
||||||
|
el.style.top = `${y}px`;
|
||||||
|
|
||||||
|
// We can use the input element to add interactible html elements in the world
|
||||||
|
imageCollection.inputElement.appendChild(el);
|
||||||
|
|
||||||
|
return el;
|
||||||
|
};
|
||||||
|
|
||||||
|
redraw();
|
||||||
|
|
||||||
|
imageSelectMenu = makeElement("div", bb.x, bb.y + bb.h);
|
||||||
|
|
||||||
|
const imageindextxt = document.createElement("button");
|
||||||
|
imageindextxt.textContent = `${at + 1}/${images.length}`;
|
||||||
|
imageindextxt.addEventListener("click", () => {
|
||||||
|
at = 0;
|
||||||
|
|
||||||
|
imageindextxt.textContent = `${at + 1}/${images.length}`;
|
||||||
|
redraw();
|
||||||
|
});
|
||||||
|
|
||||||
|
const backbtn = document.createElement("button");
|
||||||
|
backbtn.textContent = "<";
|
||||||
|
backbtn.title = "Previous Image";
|
||||||
|
backbtn.addEventListener("click", prevImg);
|
||||||
|
imageSelectMenu.appendChild(backbtn);
|
||||||
|
imageSelectMenu.appendChild(imageindextxt);
|
||||||
|
|
||||||
|
const nextbtn = document.createElement("button");
|
||||||
|
nextbtn.textContent = ">";
|
||||||
|
nextbtn.title = "Next Image";
|
||||||
|
nextbtn.addEventListener("click", nextImg);
|
||||||
|
imageSelectMenu.appendChild(nextbtn);
|
||||||
|
|
||||||
|
const morebtn = document.createElement("button");
|
||||||
|
morebtn.textContent = "+";
|
||||||
|
morebtn.title = "Generate More";
|
||||||
|
morebtn.addEventListener("click", makeMore);
|
||||||
|
imageSelectMenu.appendChild(morebtn);
|
||||||
|
|
||||||
|
const acceptbtn = document.createElement("button");
|
||||||
|
acceptbtn.textContent = "Y";
|
||||||
|
acceptbtn.title = "Apply Current";
|
||||||
|
acceptbtn.addEventListener("click", applyImg);
|
||||||
|
imageSelectMenu.appendChild(acceptbtn);
|
||||||
|
|
||||||
|
const discardbtn = document.createElement("button");
|
||||||
|
discardbtn.textContent = "N";
|
||||||
|
discardbtn.title = "Cancel";
|
||||||
|
discardbtn.addEventListener("click", discardImg);
|
||||||
|
imageSelectMenu.appendChild(discardbtn);
|
||||||
|
|
||||||
|
const resourcebtn = document.createElement("button");
|
||||||
|
resourcebtn.textContent = "R";
|
||||||
|
resourcebtn.title = "Save to Resources";
|
||||||
|
resourcebtn.addEventListener("click", async () => {
|
||||||
|
const img = new Image();
|
||||||
|
// load the image data after defining the closure
|
||||||
|
img.src = "data:image/png;base64," + images[at];
|
||||||
|
img.addEventListener("load", () => {
|
||||||
|
const response = prompt("Enter new resource name", "Dream Resource");
|
||||||
|
if (response) {
|
||||||
|
tools.stamp.state.addResource(response, img);
|
||||||
|
redraw(); // Redraw to avoid strange cursor behavior
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
imageSelectMenu.appendChild(resourcebtn);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback for generating a image (dream tool)
|
||||||
|
*
|
||||||
|
* @param {*} evn
|
||||||
|
* @param {*} state
|
||||||
|
*/
|
||||||
|
const dream_generate_callback = async (evn, state) => {
|
||||||
|
if (!blockNewImages) {
|
||||||
const bb = getBoundingBox(
|
const bb = getBoundingBox(
|
||||||
evn.x,
|
evn.x,
|
||||||
evn.y,
|
evn.y,
|
||||||
|
@ -19,9 +332,6 @@ const dream_generate_callback = (evn, state) => {
|
||||||
// Don't allow another image until is finished
|
// Don't allow another image until is finished
|
||||||
blockNewImages = true;
|
blockNewImages = true;
|
||||||
|
|
||||||
// Setup marching ants
|
|
||||||
stopMarching = march(bb);
|
|
||||||
|
|
||||||
// Setup some basic information for SD
|
// Setup some basic information for SD
|
||||||
request.width = bb.w;
|
request.width = bb.w;
|
||||||
request.height = bb.h;
|
request.height = bb.h;
|
||||||
|
@ -32,7 +342,7 @@ const dream_generate_callback = (evn, state) => {
|
||||||
// Use txt2img if canvas is blank
|
// Use txt2img if canvas is blank
|
||||||
if (isCanvasBlank(bb.x, bb.y, bb.w, bb.h, imgCanvas)) {
|
if (isCanvasBlank(bb.x, bb.y, bb.w, bb.h, imgCanvas)) {
|
||||||
// Dream
|
// Dream
|
||||||
dream(bb.x, bb.y, request, {method: "txt2img", stopMarching, bb});
|
_generate("txt2img", request, bb);
|
||||||
} else {
|
} else {
|
||||||
// Use img2img if not
|
// Use img2img if not
|
||||||
|
|
||||||
|
@ -101,7 +411,7 @@ const dream_generate_callback = (evn, state) => {
|
||||||
auxCtx.fillRect(0, 0, bb.w, bb.h);
|
auxCtx.fillRect(0, 0, bb.w, bb.h);
|
||||||
request.mask = auxCanvas.toDataURL();
|
request.mask = auxCanvas.toDataURL();
|
||||||
// Dream
|
// Dream
|
||||||
dream(bb.x, bb.y, request, {method: "img2img", stopMarching, bb});
|
_generate("img2img", request, bb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -148,7 +458,7 @@ function applyOvermask(canvas, ctx, px) {
|
||||||
* Image to Image
|
* Image to Image
|
||||||
*/
|
*/
|
||||||
const dream_img2img_callback = (evn, state) => {
|
const dream_img2img_callback = (evn, state) => {
|
||||||
if (evn.target.id === "overlayCanvas" && !blockNewImages) {
|
if (!blockNewImages) {
|
||||||
const bb = getBoundingBox(
|
const bb = getBoundingBox(
|
||||||
evn.x,
|
evn.x,
|
||||||
evn.y,
|
evn.y,
|
||||||
|
@ -174,9 +484,6 @@ const dream_img2img_callback = (evn, state) => {
|
||||||
// Don't allow another image until is finished
|
// Don't allow another image until is finished
|
||||||
blockNewImages = true;
|
blockNewImages = true;
|
||||||
|
|
||||||
// Setup marching ants
|
|
||||||
stopMarching = march(bb);
|
|
||||||
|
|
||||||
// Setup some basic information for SD
|
// Setup some basic information for SD
|
||||||
request.width = bb.w;
|
request.width = bb.w;
|
||||||
request.height = bb.h;
|
request.height = bb.h;
|
||||||
|
@ -233,7 +540,7 @@ const dream_img2img_callback = (evn, state) => {
|
||||||
request.inpaint_full_res = state.fullResolution;
|
request.inpaint_full_res = state.fullResolution;
|
||||||
|
|
||||||
// Dream
|
// Dream
|
||||||
dream(bb.x, bb.y, request, {method: "img2img", stopMarching, bb});
|
_generate("img2img", request, bb);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -248,23 +555,22 @@ const dreamTool = () =>
|
||||||
// Draw new cursor immediately
|
// Draw new cursor immediately
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||||
state.mousemovecb({
|
state.mousemovecb({
|
||||||
...mouse.coords.canvas.pos,
|
...mouse.coords.world.pos,
|
||||||
target: {id: "overlayCanvas"},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start Listeners
|
// Start Listeners
|
||||||
mouse.listen.canvas.onmousemove.on(state.mousemovecb);
|
mouse.listen.world.onmousemove.on(state.mousemovecb);
|
||||||
mouse.listen.canvas.btn.left.onclick.on(state.dreamcb);
|
mouse.listen.world.btn.left.onclick.on(state.dreamcb);
|
||||||
mouse.listen.canvas.btn.right.onclick.on(state.erasecb);
|
mouse.listen.world.btn.right.onclick.on(state.erasecb);
|
||||||
|
|
||||||
// Display Mask
|
// Display Mask
|
||||||
setMask(state.invertMask ? "hold" : "clear");
|
setMask(state.invertMask ? "hold" : "clear");
|
||||||
},
|
},
|
||||||
(state, opt) => {
|
(state, opt) => {
|
||||||
// Clear Listeners
|
// Clear Listeners
|
||||||
mouse.listen.canvas.onmousemove.clear(state.mousemovecb);
|
mouse.listen.world.onmousemove.clear(state.mousemovecb);
|
||||||
mouse.listen.canvas.btn.left.onclick.clear(state.dreamcb);
|
mouse.listen.world.btn.left.onclick.clear(state.dreamcb);
|
||||||
mouse.listen.canvas.btn.right.onclick.clear(state.erasecb);
|
mouse.listen.world.btn.right.onclick.clear(state.erasecb);
|
||||||
|
|
||||||
// Hide Mask
|
// Hide Mask
|
||||||
setMask("none");
|
setMask("none");
|
||||||
|
@ -274,7 +580,10 @@ const dreamTool = () =>
|
||||||
state.snapToGrid = true;
|
state.snapToGrid = true;
|
||||||
state.invertMask = false;
|
state.invertMask = false;
|
||||||
state.overMaskPx = 0;
|
state.overMaskPx = 0;
|
||||||
state.mousemovecb = (evn) => _reticle_draw(evn, state.snapToGrid);
|
state.mousemovecb = (evn) => {
|
||||||
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||||
|
_reticle_draw(evn, state.snapToGrid);
|
||||||
|
};
|
||||||
state.dreamcb = (evn) => {
|
state.dreamcb = (evn) => {
|
||||||
dream_generate_callback(evn, state);
|
dream_generate_callback(evn, state);
|
||||||
};
|
};
|
||||||
|
@ -330,23 +639,22 @@ const img2imgTool = () =>
|
||||||
// Draw new cursor immediately
|
// Draw new cursor immediately
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||||
state.mousemovecb({
|
state.mousemovecb({
|
||||||
...mouse.coords.canvas.pos,
|
...mouse.coords.world.pos,
|
||||||
target: {id: "overlayCanvas"},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start Listeners
|
// Start Listeners
|
||||||
mouse.listen.canvas.onmousemove.on(state.mousemovecb);
|
mouse.listen.world.onmousemove.on(state.mousemovecb);
|
||||||
mouse.listen.canvas.btn.left.onclick.on(state.dreamcb);
|
mouse.listen.world.btn.left.onclick.on(state.dreamcb);
|
||||||
mouse.listen.canvas.btn.right.onclick.on(state.erasecb);
|
mouse.listen.world.btn.right.onclick.on(state.erasecb);
|
||||||
|
|
||||||
// Display Mask
|
// Display Mask
|
||||||
setMask(state.invertMask ? "hold" : "clear");
|
setMask(state.invertMask ? "hold" : "clear");
|
||||||
},
|
},
|
||||||
(state, opt) => {
|
(state, opt) => {
|
||||||
// Clear Listeners
|
// Clear Listeners
|
||||||
mouse.listen.canvas.onmousemove.clear(state.mousemovecb);
|
mouse.listen.world.onmousemove.clear(state.mousemovecb);
|
||||||
mouse.listen.canvas.btn.left.onclick.clear(state.dreamcb);
|
mouse.listen.world.btn.left.onclick.clear(state.dreamcb);
|
||||||
mouse.listen.canvas.btn.right.onclick.clear(state.erasecb);
|
mouse.listen.world.btn.right.onclick.clear(state.erasecb);
|
||||||
|
|
||||||
// Hide mask
|
// Hide mask
|
||||||
setMask("none");
|
setMask("none");
|
||||||
|
@ -362,8 +670,8 @@ const img2imgTool = () =>
|
||||||
state.keepBorderSize = 64;
|
state.keepBorderSize = 64;
|
||||||
|
|
||||||
state.mousemovecb = (evn) => {
|
state.mousemovecb = (evn) => {
|
||||||
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||||
_reticle_draw(evn, state.snapToGrid);
|
_reticle_draw(evn, state.snapToGrid);
|
||||||
if (evn.target.id === "overlayCanvas") {
|
|
||||||
const bb = getBoundingBox(
|
const bb = getBoundingBox(
|
||||||
evn.x,
|
evn.x,
|
||||||
evn.y,
|
evn.y,
|
||||||
|
@ -400,7 +708,6 @@ const img2imgTool = () =>
|
||||||
ovCtx.globalAlpha = 0.4;
|
ovCtx.globalAlpha = 0.4;
|
||||||
ovCtx.drawImage(auxCanvas, bb.x, bb.y);
|
ovCtx.drawImage(auxCanvas, bb.x, bb.y);
|
||||||
ovCtx.globalAlpha = tmp;
|
ovCtx.globalAlpha = tmp;
|
||||||
}
|
|
||||||
};
|
};
|
||||||
state.dreamcb = (evn) => {
|
state.dreamcb = (evn) => {
|
||||||
dream_img2img_callback(evn, state);
|
dream_img2img_callback(evn, state);
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
const setMask = (state) => {
|
const setMask = (state) => {
|
||||||
const canvas = document.querySelector("#maskPaintCanvas");
|
const canvas = imageCollection.layers.mask.canvas;
|
||||||
switch (state) {
|
switch (state) {
|
||||||
case "clear":
|
case "clear":
|
||||||
canvas.classList.remove("hold");
|
canvas.classList.remove("hold");
|
||||||
|
@ -23,10 +23,6 @@ const setMask = (state) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const _mask_brush_draw_callback = (evn, state) => {
|
const _mask_brush_draw_callback = (evn, state) => {
|
||||||
if (
|
|
||||||
(evn.initialTarget && evn.initialTarget.id === "overlayCanvas") ||
|
|
||||||
(!evn.initialTarget && evn.target.id === "overlayCanvas")
|
|
||||||
) {
|
|
||||||
maskPaintCtx.globalCompositeOperation = "source-over";
|
maskPaintCtx.globalCompositeOperation = "source-over";
|
||||||
maskPaintCtx.strokeStyle = "black";
|
maskPaintCtx.strokeStyle = "black";
|
||||||
|
|
||||||
|
@ -39,14 +35,9 @@ const _mask_brush_draw_callback = (evn, state) => {
|
||||||
maskPaintCtx.lineTo(evn.x, evn.y);
|
maskPaintCtx.lineTo(evn.x, evn.y);
|
||||||
maskPaintCtx.lineJoin = maskPaintCtx.lineCap = "round";
|
maskPaintCtx.lineJoin = maskPaintCtx.lineCap = "round";
|
||||||
maskPaintCtx.stroke();
|
maskPaintCtx.stroke();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const _mask_brush_erase_callback = (evn, state) => {
|
const _mask_brush_erase_callback = (evn, state) => {
|
||||||
if (
|
|
||||||
(evn.initialTarget && evn.initialTarget.id === "overlayCanvas") ||
|
|
||||||
(!evn.initialTarget && evn.target.id === "overlayCanvas")
|
|
||||||
) {
|
|
||||||
maskPaintCtx.globalCompositeOperation = "destination-out";
|
maskPaintCtx.globalCompositeOperation = "destination-out";
|
||||||
maskPaintCtx.strokeStyle = "black";
|
maskPaintCtx.strokeStyle = "black";
|
||||||
|
|
||||||
|
@ -59,7 +50,6 @@ const _mask_brush_erase_callback = (evn, state) => {
|
||||||
maskPaintCtx.lineTo(evn.x, evn.y);
|
maskPaintCtx.lineTo(evn.x, evn.y);
|
||||||
maskPaintCtx.lineJoin = maskPaintCtx.lineCap = "round";
|
maskPaintCtx.lineJoin = maskPaintCtx.lineCap = "round";
|
||||||
maskPaintCtx.stroke();
|
maskPaintCtx.stroke();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const maskBrushTool = () =>
|
const maskBrushTool = () =>
|
||||||
|
@ -69,27 +59,27 @@ const maskBrushTool = () =>
|
||||||
(state, opt) => {
|
(state, opt) => {
|
||||||
// Draw new cursor immediately
|
// Draw new cursor immediately
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||||
state.movecb({...mouse.coords.canvas.pos, target: {id: "overlayCanvas"}});
|
state.movecb({...mouse.coords.world.pos});
|
||||||
|
|
||||||
// Start Listeners
|
// Start Listeners
|
||||||
mouse.listen.canvas.onmousemove.on(state.movecb);
|
mouse.listen.world.onmousemove.on(state.movecb);
|
||||||
mouse.listen.canvas.onwheel.on(state.wheelcb);
|
mouse.listen.world.onwheel.on(state.wheelcb);
|
||||||
mouse.listen.canvas.btn.left.onpaintstart.on(state.drawcb);
|
mouse.listen.world.btn.left.onpaintstart.on(state.drawcb);
|
||||||
mouse.listen.canvas.btn.left.onpaint.on(state.drawcb);
|
mouse.listen.world.btn.left.onpaint.on(state.drawcb);
|
||||||
mouse.listen.canvas.btn.right.onpaintstart.on(state.erasecb);
|
mouse.listen.world.btn.right.onpaintstart.on(state.erasecb);
|
||||||
mouse.listen.canvas.btn.right.onpaint.on(state.erasecb);
|
mouse.listen.world.btn.right.onpaint.on(state.erasecb);
|
||||||
|
|
||||||
// Display Mask
|
// Display Mask
|
||||||
setMask("neutral");
|
setMask("neutral");
|
||||||
},
|
},
|
||||||
(state, opt) => {
|
(state, opt) => {
|
||||||
// Clear Listeners
|
// Clear Listeners
|
||||||
mouse.listen.canvas.onmousemove.clear(state.movecb);
|
mouse.listen.world.onmousemove.clear(state.movecb);
|
||||||
mouse.listen.canvas.onwheel.clear(state.wheelcb);
|
mouse.listen.world.onwheel.clear(state.wheelcb);
|
||||||
mouse.listen.canvas.btn.left.onpaintstart.clear(state.drawcb);
|
mouse.listen.world.btn.left.onpaintstart.clear(state.drawcb);
|
||||||
mouse.listen.canvas.btn.left.onpaint.clear(state.drawcb);
|
mouse.listen.world.btn.left.onpaint.clear(state.drawcb);
|
||||||
mouse.listen.canvas.btn.right.onpaintstart.clear(state.erasecb);
|
mouse.listen.world.btn.right.onpaintstart.clear(state.erasecb);
|
||||||
mouse.listen.canvas.btn.right.onpaint.clear(state.erasecb);
|
mouse.listen.world.btn.right.onpaint.clear(state.erasecb);
|
||||||
|
|
||||||
// Hide Mask
|
// Hide Mask
|
||||||
setMask("none");
|
setMask("none");
|
||||||
|
@ -115,8 +105,8 @@ const maskBrushTool = () =>
|
||||||
state.preview = false;
|
state.preview = false;
|
||||||
|
|
||||||
state.movecb = (evn) => {
|
state.movecb = (evn) => {
|
||||||
if (evn.target.id === "overlayCanvas") {
|
|
||||||
// draw big translucent white blob cursor
|
// draw big translucent white blob cursor
|
||||||
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||||
ovCtx.beginPath();
|
ovCtx.beginPath();
|
||||||
ovCtx.arc(evn.x, evn.y, state.brushSize / 2, 0, 2 * Math.PI, true); // for some reason 4x on an arc is === to 8x on a line???
|
ovCtx.arc(evn.x, evn.y, state.brushSize / 2, 0, 2 * Math.PI, true); // for some reason 4x on an arc is === to 8x on a line???
|
||||||
ovCtx.fillStyle = "#FFFFFF50";
|
ovCtx.fillStyle = "#FFFFFF50";
|
||||||
|
@ -129,11 +119,10 @@ const maskBrushTool = () =>
|
||||||
ovCtx.stroke();
|
ovCtx.stroke();
|
||||||
ovCtx.setLineDash([]);
|
ovCtx.setLineDash([]);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
state.wheelcb = (evn) => {
|
state.wheelcb = (evn) => {
|
||||||
if (evn.target.id === "overlayCanvas") {
|
if (!evn.evn.ctrlKey) {
|
||||||
state.brushSize = state.setBrushSize(
|
state.brushSize = state.setBrushSize(
|
||||||
state.brushSize -
|
state.brushSize -
|
||||||
Math.floor(state.config.brushScrollSpeed * evn.delta)
|
Math.floor(state.config.brushScrollSpeed * evn.delta)
|
||||||
|
|
|
@ -5,16 +5,16 @@ const selectTransformTool = () =>
|
||||||
(state, opt) => {
|
(state, opt) => {
|
||||||
// Draw new cursor immediately
|
// Draw new cursor immediately
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||||
state.movecb({...mouse.coords.canvas.pos, target: {id: "overlayCanvas"}});
|
state.movecb(mouse.coords.world.pos);
|
||||||
|
|
||||||
// Canvas left mouse handlers
|
// Canvas left mouse handlers
|
||||||
mouse.listen.canvas.onmousemove.on(state.movecb);
|
mouse.listen.world.onmousemove.on(state.movecb);
|
||||||
mouse.listen.canvas.btn.left.onclick.on(state.clickcb);
|
mouse.listen.world.btn.left.onclick.on(state.clickcb);
|
||||||
mouse.listen.canvas.btn.left.ondragstart.on(state.dragstartcb);
|
mouse.listen.world.btn.left.ondragstart.on(state.dragstartcb);
|
||||||
mouse.listen.canvas.btn.left.ondragend.on(state.dragendcb);
|
mouse.listen.world.btn.left.ondragend.on(state.dragendcb);
|
||||||
|
|
||||||
// Canvas right mouse handler
|
// Canvas right mouse handler
|
||||||
mouse.listen.canvas.btn.right.onclick.on(state.cancelcb);
|
mouse.listen.world.btn.right.onclick.on(state.cancelcb);
|
||||||
|
|
||||||
// Keyboard click handlers
|
// Keyboard click handlers
|
||||||
keyboard.listen.onkeyclick.on(state.keyclickcb);
|
keyboard.listen.onkeyclick.on(state.keyclickcb);
|
||||||
|
@ -29,12 +29,12 @@ const selectTransformTool = () =>
|
||||||
},
|
},
|
||||||
(state, opt) => {
|
(state, opt) => {
|
||||||
// Clear all those listeners and shortcuts we set up
|
// Clear all those listeners and shortcuts we set up
|
||||||
mouse.listen.canvas.onmousemove.clear(state.movecb);
|
mouse.listen.world.onmousemove.clear(state.movecb);
|
||||||
mouse.listen.canvas.btn.left.onclick.clear(state.clickcb);
|
mouse.listen.world.btn.left.onclick.clear(state.clickcb);
|
||||||
mouse.listen.canvas.btn.left.ondragstart.clear(state.dragstartcb);
|
mouse.listen.world.btn.left.ondragstart.clear(state.dragstartcb);
|
||||||
mouse.listen.canvas.btn.left.ondragend.clear(state.dragendcb);
|
mouse.listen.world.btn.left.ondragend.clear(state.dragendcb);
|
||||||
|
|
||||||
mouse.listen.canvas.btn.right.onclick.clear(state.cancelcb);
|
mouse.listen.world.btn.right.onclick.clear(state.cancelcb);
|
||||||
|
|
||||||
keyboard.listen.onkeyclick.clear(state.keyclickcb);
|
keyboard.listen.onkeyclick.clear(state.keyclickcb);
|
||||||
keyboard.listen.onkeydown.clear(state.keydowncb);
|
keyboard.listen.onkeydown.clear(state.keydowncb);
|
||||||
|
@ -46,7 +46,7 @@ const selectTransformTool = () =>
|
||||||
state.reset();
|
state.reset();
|
||||||
|
|
||||||
// Resets cursor
|
// Resets cursor
|
||||||
ovCanvas.style.cursor = "auto";
|
imageCollection.inputElement.style.cursor = "auto";
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
init: (state) => {
|
init: (state) => {
|
||||||
|
@ -183,12 +183,12 @@ const selectTransformTool = () =>
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Mouse move handelr. As always, also renders cursor
|
// Mouse move handler. As always, also renders cursor
|
||||||
state.movecb = (evn) => {
|
state.movecb = (evn) => {
|
||||||
ovCanvas.style.cursor = "auto";
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||||
|
imageCollection.inputElement.style.cursor = "auto";
|
||||||
state.lastMouseTarget = evn.target;
|
state.lastMouseTarget = evn.target;
|
||||||
state.lastMouseMove = evn;
|
state.lastMouseMove = evn;
|
||||||
if (evn.target.id === "overlayCanvas") {
|
|
||||||
let x = evn.x;
|
let x = evn.x;
|
||||||
let y = evn.y;
|
let y = evn.y;
|
||||||
if (state.snapToGrid) {
|
if (state.snapToGrid) {
|
||||||
|
@ -281,7 +281,7 @@ const selectTransformTool = () =>
|
||||||
|
|
||||||
// Change cursor
|
// Change cursor
|
||||||
if (cursorInHandle || state.selected.contains(evn.x, evn.y))
|
if (cursorInHandle || state.selected.contains(evn.x, evn.y))
|
||||||
ovCanvas.style.cursor = "pointer";
|
imageCollection.inputElement.style.cursor = "pointer";
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw current cursor location
|
// Draw current cursor location
|
||||||
|
@ -294,12 +294,20 @@ const selectTransformTool = () =>
|
||||||
ovCtx.moveTo(x + 10, y);
|
ovCtx.moveTo(x + 10, y);
|
||||||
ovCtx.lineTo(x - 10, y);
|
ovCtx.lineTo(x - 10, y);
|
||||||
ovCtx.stroke();
|
ovCtx.stroke();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handles left mouse clicks
|
// Handles left mouse clicks
|
||||||
state.clickcb = (evn) => {
|
state.clickcb = (evn) => {
|
||||||
if (evn.target.id === "overlayCanvas") {
|
if (
|
||||||
|
state.original.x === state.selected.x &&
|
||||||
|
state.original.y === state.selected.y &&
|
||||||
|
state.original.w === state.selected.w &&
|
||||||
|
state.original.h === state.selected.h
|
||||||
|
) {
|
||||||
|
state.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// If something is selected, commit changes to the canvas
|
// If something is selected, commit changes to the canvas
|
||||||
if (state.selected) {
|
if (state.selected) {
|
||||||
imgCtx.drawImage(
|
imgCtx.drawImage(
|
||||||
|
@ -322,12 +330,10 @@ const selectTransformTool = () =>
|
||||||
|
|
||||||
redraw();
|
redraw();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handles left mouse drag events
|
// Handles left mouse drag events
|
||||||
state.dragstartcb = (evn) => {
|
state.dragstartcb = (evn) => {
|
||||||
if (evn.target.id === "overlayCanvas") {
|
|
||||||
let ix = evn.ix;
|
let ix = evn.ix;
|
||||||
let iy = evn.iy;
|
let iy = evn.iy;
|
||||||
if (state.snapToGrid) {
|
if (state.snapToGrid) {
|
||||||
|
@ -355,12 +361,10 @@ const selectTransformTool = () =>
|
||||||
// If it is not, just create new selection
|
// If it is not, just create new selection
|
||||||
state.reset();
|
state.reset();
|
||||||
state.dragging = {ix, iy};
|
state.dragging = {ix, iy};
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handles left mouse drag end events
|
// Handles left mouse drag end events
|
||||||
state.dragendcb = (evn) => {
|
state.dragendcb = (evn) => {
|
||||||
if (evn.target.id === "overlayCanvas") {
|
|
||||||
let x = evn.x;
|
let x = evn.x;
|
||||||
let y = evn.y;
|
let y = evn.y;
|
||||||
if (state.snapToGrid) {
|
if (state.snapToGrid) {
|
||||||
|
@ -426,41 +430,31 @@ const selectTransformTool = () =>
|
||||||
state.dragging = null;
|
state.dragging = null;
|
||||||
}
|
}
|
||||||
redraw();
|
redraw();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handler for right clicks. Basically resets everything
|
// Handler for right clicks. Basically resets everything
|
||||||
state.cancelcb = (evn) => {
|
state.cancelcb = (evn) => {
|
||||||
if (evn.target.id === "overlayCanvas") {
|
|
||||||
state.reset();
|
state.reset();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Keyboard callbacks (For now, they just handle the "delete" key)
|
// Keyboard callbacks (For now, they just handle the "delete" key)
|
||||||
state.keydowncb = (evn) => {};
|
state.keydowncb = (evn) => {};
|
||||||
|
|
||||||
state.keyclickcb = (evn) => {
|
state.keyclickcb = (evn) => {
|
||||||
if (state.lastMouseTarget.id === "overlayCanvas") {
|
|
||||||
switch (evn.code) {
|
switch (evn.code) {
|
||||||
case "Delete":
|
case "Delete":
|
||||||
// Deletes selected area
|
// Deletes selected area
|
||||||
state.selected &&
|
state.selected &&
|
||||||
commands.runCommand(
|
commands.runCommand("eraseImage", "Erase Area", state.selected);
|
||||||
"eraseImage",
|
|
||||||
"Erase Area",
|
|
||||||
state.selected
|
|
||||||
);
|
|
||||||
state.selected = null;
|
state.selected = null;
|
||||||
redraw();
|
redraw();
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Register Ctrl-C/V Shortcut
|
// Register Ctrl-C/V Shortcut
|
||||||
|
|
||||||
// Handles copying
|
// Handles copying
|
||||||
state.ctrlccb = (evn, cut = false) => {
|
state.ctrlccb = (evn, cut = false) => {
|
||||||
if (state.selected && state.lastMouseTarget.id === "overlayCanvas") {
|
|
||||||
// We create a new canvas to store the data
|
// We create a new canvas to store the data
|
||||||
state.clipboard.copy = document.createElement("canvas");
|
state.clipboard.copy = document.createElement("canvas");
|
||||||
|
|
||||||
|
@ -501,7 +495,6 @@ const selectTransformTool = () =>
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Handles pasting
|
// Handles pasting
|
||||||
|
|
|
@ -3,14 +3,16 @@ const stampTool = () =>
|
||||||
"res/icons/file-up.svg",
|
"res/icons/file-up.svg",
|
||||||
"Stamp Image",
|
"Stamp Image",
|
||||||
(state, opt) => {
|
(state, opt) => {
|
||||||
|
state.loaded = true;
|
||||||
|
|
||||||
// Draw new cursor immediately
|
// Draw new cursor immediately
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||||
state.movecb({...mouse.coords.canvas.pos, target: {id: "overlayCanvas"}});
|
state.movecb({...mouse.coords.world.pos});
|
||||||
|
|
||||||
// Start Listeners
|
// Start Listeners
|
||||||
mouse.listen.canvas.onmousemove.on(state.movecb);
|
mouse.listen.world.onmousemove.on(state.movecb);
|
||||||
mouse.listen.canvas.btn.left.onclick.on(state.drawcb);
|
mouse.listen.world.btn.left.onclick.on(state.drawcb);
|
||||||
mouse.listen.canvas.btn.right.onclick.on(state.cancelcb);
|
mouse.listen.world.btn.right.onclick.on(state.cancelcb);
|
||||||
|
|
||||||
// For calls from other tools to paste image
|
// For calls from other tools to paste image
|
||||||
if (opt && opt.image) {
|
if (opt && opt.image) {
|
||||||
|
@ -31,10 +33,12 @@ const stampTool = () =>
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
(state, opt) => {
|
(state, opt) => {
|
||||||
|
state.loaded = false;
|
||||||
|
|
||||||
// Clear Listeners
|
// Clear Listeners
|
||||||
mouse.listen.canvas.onmousemove.clear(state.movecb);
|
mouse.listen.world.onmousemove.clear(state.movecb);
|
||||||
mouse.listen.canvas.btn.left.onclick.clear(state.drawcb);
|
mouse.listen.world.btn.left.onclick.clear(state.drawcb);
|
||||||
mouse.listen.canvas.btn.right.onclick.clear(state.cancelcb);
|
mouse.listen.world.btn.right.onclick.clear(state.cancelcb);
|
||||||
|
|
||||||
// Deselect
|
// Deselect
|
||||||
state.selected = null;
|
state.selected = null;
|
||||||
|
@ -44,6 +48,7 @@ const stampTool = () =>
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
init: (state) => {
|
init: (state) => {
|
||||||
|
state.loaded = false;
|
||||||
state.snapToGrid = true;
|
state.snapToGrid = true;
|
||||||
state.resources = [];
|
state.resources = [];
|
||||||
state.selected = null;
|
state.selected = null;
|
||||||
|
@ -75,7 +80,7 @@ const stampTool = () =>
|
||||||
}
|
}
|
||||||
|
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||||
state.movecb(state.lastMouseMove);
|
if (state.loaded) state.movecb(state.lastMouseMove);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Synchronizes resources array with the DOM
|
// Synchronizes resources array with the DOM
|
||||||
|
@ -153,7 +158,6 @@ const stampTool = () =>
|
||||||
};
|
};
|
||||||
|
|
||||||
state.movecb = (evn) => {
|
state.movecb = (evn) => {
|
||||||
if (evn.target && evn.target.id === "overlayCanvas") {
|
|
||||||
let x = evn.x;
|
let x = evn.x;
|
||||||
let y = evn.y;
|
let y = evn.y;
|
||||||
if (state.snapToGrid) {
|
if (state.snapToGrid) {
|
||||||
|
@ -163,6 +167,8 @@ const stampTool = () =>
|
||||||
|
|
||||||
state.lastMouseMove = evn;
|
state.lastMouseMove = evn;
|
||||||
|
|
||||||
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||||
|
|
||||||
// Draw selected image
|
// Draw selected image
|
||||||
if (state.selected) {
|
if (state.selected) {
|
||||||
ovCtx.drawImage(state.selected.image, x, y);
|
ovCtx.drawImage(state.selected.image, x, y);
|
||||||
|
@ -178,11 +184,9 @@ const stampTool = () =>
|
||||||
ovCtx.moveTo(x + 10, y);
|
ovCtx.moveTo(x + 10, y);
|
||||||
ovCtx.lineTo(x - 10, y);
|
ovCtx.lineTo(x - 10, y);
|
||||||
ovCtx.stroke();
|
ovCtx.stroke();
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
state.drawcb = (evn) => {
|
state.drawcb = (evn) => {
|
||||||
if (evn.target.id === "overlayCanvas") {
|
|
||||||
let x = evn.x;
|
let x = evn.x;
|
||||||
let y = evn.y;
|
let y = evn.y;
|
||||||
if (state.snapToGrid) {
|
if (state.snapToGrid) {
|
||||||
|
@ -208,10 +212,8 @@ const stampTool = () =>
|
||||||
state.back = null;
|
state.back = null;
|
||||||
backfn({message: "Returning from stamp", pasted: true});
|
backfn({message: "Returning from stamp", pasted: true});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
state.cancelcb = (evn) => {
|
state.cancelcb = (evn) => {
|
||||||
if (evn.target.id === "overlayCanvas") {
|
|
||||||
state.selectResource(null);
|
state.selectResource(null);
|
||||||
|
|
||||||
if (state.back) {
|
if (state.back) {
|
||||||
|
@ -220,7 +222,6 @@ const stampTool = () =>
|
||||||
state.back = null;
|
state.back = null;
|
||||||
backfn({message: "Returning from stamp", pasted: false});
|
backfn({message: "Returning from stamp", pasted: false});
|
||||||
}
|
}
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in a new issue