initial commit
This commit is contained in:
parent
a91417e121
commit
87e4ff4c2f
2 changed files with 818 additions and 0 deletions
256
index.html
Normal file
256
index.html
Normal file
|
@ -0,0 +1,256 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en-US">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>openOutpaint 0.0.4</title>
|
||||
|
||||
<style>
|
||||
.container {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.backgroundCanvas {
|
||||
background-color: #ccc;
|
||||
}
|
||||
|
||||
.maskPaintCanvas {
|
||||
border: 3px dotted #993355C0
|
||||
}
|
||||
|
||||
.overlayCanvas {
|
||||
border: 1px solid #F00;
|
||||
}
|
||||
|
||||
.tempCanvas {
|
||||
border: 3px dotted #007AFFC0;
|
||||
}
|
||||
|
||||
.targetCanvas {
|
||||
border: 2px dashed #0F0;
|
||||
}
|
||||
|
||||
.canvas {
|
||||
border: 2px dotted #00F;
|
||||
}
|
||||
|
||||
.mainHSplit {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: repeat(2, 1fr);
|
||||
grid-column-gap: 5px;
|
||||
grid-row-gap: 5px;
|
||||
}
|
||||
|
||||
.uiWrapper {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 15fr;
|
||||
grid-template-rows: 1fr;
|
||||
grid-column-gap: 5px;
|
||||
grid-row-gap: 5px;
|
||||
}
|
||||
|
||||
.canvasHolder {
|
||||
position: relative;
|
||||
width: 2560px;
|
||||
height: 1440px;
|
||||
}
|
||||
|
||||
.mainCanvases {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
left: 0px;
|
||||
width: 2560px;
|
||||
height: 1440px;
|
||||
}
|
||||
|
||||
.masks {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
grid-template-rows: 1fr;
|
||||
grid-column-gap: 0px;
|
||||
grid-row-gap: 0px;
|
||||
}
|
||||
|
||||
.maskCanvas {
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.initImgCanvas {
|
||||
position: absolute;
|
||||
left: 600px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="mainHSplit" class="mainHSplit">
|
||||
<div id="uiWrapper" class="uiWrapper">
|
||||
<div id="info" class="info" style="min-width:200px;">
|
||||
<div id="coords">
|
||||
<label for="mouseX">mouseX:</label>
|
||||
<span id="mouseX"></span>
|
||||
<br />
|
||||
<label for="mouseY">mouseY:</label>
|
||||
<span id="mouseY"></span>
|
||||
<br />
|
||||
<label for="canvasX">canvasX:</label>
|
||||
<span id="canvasX"></span>
|
||||
<br />
|
||||
<label for="canvasY">canvasY:</label>
|
||||
<span id="canvasY"></span>
|
||||
<br />
|
||||
<label for="snapX">snapX:</label>
|
||||
<span id="snapX"></span>
|
||||
<br />
|
||||
<label for="snapY">snapY:</label>
|
||||
<span id="snapY"></span>
|
||||
<br />
|
||||
</div>
|
||||
<label for="host">host:</label>
|
||||
<input id="host" value="http://127.0.0.1:7860"><br />
|
||||
<label for="scaleFactor">scale factor:</label>
|
||||
<span id="scaleFactorTxt"></span><br />
|
||||
<input type="range" id="scaleFactor" name="scaleFactor" min="1" max="16" value="8"
|
||||
onchange="changeScaleFactor()" /><br />
|
||||
<label for="prompt">prompt:</label>
|
||||
<textarea
|
||||
id="prompt">oceanographic study, underwater wildlife, award winning documentary footage screenshot</textarea><br />
|
||||
<label for="negPrompt">negative prompt:</label>
|
||||
<textarea
|
||||
id="negPrompt">people, humans, divers, glitch, error, text, watermark, bad quality, blurry</textarea><br />
|
||||
<label for="cbxSnap">snap to grid?</label>
|
||||
<input type="checkbox" id="cbxSnap" onchange="changeSnapMode()"><br />
|
||||
<label for="cbxPaint">mask mode?</label>
|
||||
<input type="checkbox" id="cbxPaint" onchange="changePaintMode()"><br />
|
||||
<label for="cbxErase">erase mask?</label>
|
||||
<input type="checkbox" id="cbxErase" onchange="changeEraseMode()" disabled="disabled"><br />
|
||||
<label for="samplerSelect">sampler:</label>
|
||||
<select id="samplerSelect" onchange="changeSampler()">
|
||||
<option value="DDIM">DDIM</option>
|
||||
<option value="Euler a">Euler A</option>
|
||||
<option value="Euler">Euler</option>
|
||||
<option value="LMS">LMS</option>
|
||||
<option value="Heun">Heun</option>
|
||||
<option value="DPM2">DPM2</option>
|
||||
<option value="DPM2 a">DPM2a</option>
|
||||
<option value="DPM++ 2S a">DPM++2Sa</option>
|
||||
<option value="DPM++ 2m">DPM++2m</option>
|
||||
<option value="DPM fast">DPM fast</option>
|
||||
<option value="DPM adaptive">DPM adaptive</option>
|
||||
<option value="LMS Karras">LMS Karras</option>
|
||||
<option value="DPM2 Karras">DPM2 Karras</option>
|
||||
<option value="DPM2 a Karras">DPM2a Karras</option>
|
||||
<option value="DPM++ 2S a Karras">DPM++2Sa Karras</option>
|
||||
<option value="DPM++ 2M Karras">DPM++2M Karras</option>
|
||||
</select><br />
|
||||
<label for="steps">steps:</label>
|
||||
<span id="stepsTxt"></span><br />
|
||||
<input type="range" id="steps" name="steps" min="1" max="50" value="30"
|
||||
onchange="changeSteps()" /><br />
|
||||
<label for="cfgScale">CFG scale:</label>
|
||||
<span id="cfgScaleTxt"></span><br />
|
||||
<input type="range" id="cfgScale" name="cfgScale" min="-1" max="25" value="7.5" step="0.1"
|
||||
list="cfgDetents" onchange="changeCfgScale()" />
|
||||
<datalist id="cfgDetents">
|
||||
<option value="-1"></option>
|
||||
<option value="0"></option>
|
||||
<option value="1"></option>
|
||||
<option value="2"></option>
|
||||
<option value="3"></option>
|
||||
<option value="4"></option>
|
||||
<option value="5"></option>
|
||||
<option value="6"></option>
|
||||
<option value="7"></option>
|
||||
<option value="7.5"></option>
|
||||
<option value="8"></option>
|
||||
<option value="9"></option>
|
||||
<option value="10"></option>
|
||||
<option value="11"></option>
|
||||
<option value="12"></option>
|
||||
<option value="13"></option>
|
||||
<option value="14"></option>
|
||||
<option value="15"></option>
|
||||
<option value="16"></option>
|
||||
<option value="17"></option>
|
||||
<option value="18"></option>
|
||||
<option value="19"></option>
|
||||
<option value="20"></option>
|
||||
<option value="21"></option>
|
||||
<option value="22"></option>
|
||||
<option value="23"></option>
|
||||
<option value="24"></option>
|
||||
</datalist><br />
|
||||
<label for="batchSize">batch size:</label>
|
||||
<span id="batchSizeText"></span><br />
|
||||
<input type="range" id="batchSize" name="batchSize" min="1" max="8" value="2" step="1" list="cfgDetents"
|
||||
onchange="changeBatchSize()" /><br />
|
||||
<label for="batchCount">batch count:</label>
|
||||
<span id="batchCountText"></span><br />
|
||||
<input type="range" id="batchCount" name="batchCount" min="1" max="8" value="2" step="1"
|
||||
onchange="changeBatchCount()" /><br />
|
||||
<label for="maskBlur">mask blur:</label>
|
||||
<span id="maskBlurText"></span><br />
|
||||
<input type="number" id="maskBlur" name="maskBlur" min="0" max="64" value="0" step="1"
|
||||
onchange="changeMaskBlur()" /><br />
|
||||
<button onclick="downloadImage()">dl img</button>
|
||||
</div>
|
||||
<div id="canvasHolder" class="canvasHolder">
|
||||
<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 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" hidden="hidden">
|
||||
<div>
|
||||
<canvas id="maskCanvas" class="maskCanvas" width="512" height="512">
|
||||
<p>lol ur browser sucks</p>
|
||||
</canvas>
|
||||
<canvas id="initImgCanvas" class="initImgCanvas" width="512" height="512">
|
||||
<p>lol ur browser sucks</p>
|
||||
</canvas>
|
||||
<label hidden="hidden">Drawing tool:
|
||||
<select id="dtool" hidden="hidden">
|
||||
<option value="SDimg">SDimg</option>
|
||||
<option value="rect">Rectangle</option>
|
||||
<option value="pencil">Pencil</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script src="js/index.js" type="text/javascript"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
562
js/index.js
Normal file
562
js/index.js
Normal file
|
@ -0,0 +1,562 @@
|
|||
//TODO VITALLY IMPORTANT SERIOUSLY HOLY SHIT
|
||||
//FIND OUT WHY I HAVE TO RESIZE A TEXTBOX AND THEN START USING IT TO AVOID THE 1px WHITE LINE ON LEFT EDGES DURING IMG2IMG
|
||||
//lmao did setting min width 200 on info div fix that
|
||||
|
||||
//TODO also fix my stupid offset -- what does this mean ??? oh, was that re: when f12 is open, the reticle is offset vertically from where the target location actually is?
|
||||
|
||||
//lmao ok auto1111 webui api allows this if you add "null" to the cors allow list which seems like a bad plan so i recommend using a lil webhost
|
||||
|
||||
window.onload = startup;
|
||||
|
||||
var stableDiffusionData = { //includes img2img data but works for txt2img just fine
|
||||
prompt: "",
|
||||
negative_prompt: "",
|
||||
seed: -1,
|
||||
cfg_scale: 7,
|
||||
sampler_index: "DDIM",
|
||||
steps: 30,
|
||||
denoising_strength: 1,
|
||||
mask_blur: 0,
|
||||
batch_size: 2,
|
||||
width: 512,
|
||||
height: 512,
|
||||
n_iter: 2, // batch count
|
||||
mask: "",
|
||||
init_images: [],
|
||||
inpaint_full_res: false,
|
||||
inpainting_fill: 2,
|
||||
// here's some more fields that might be useful
|
||||
|
||||
// ---txt2img specific:
|
||||
// "enable_hr": false, // hires fix
|
||||
// "denoising_strength": 0, // ok this is in both txt and img2img but txt2img only applies it if enable_hr == true
|
||||
// "firstphase_width": 0, // hires fp w
|
||||
// "firstphase_height": 0, // see above s/w/h/
|
||||
|
||||
// ---img2img specific
|
||||
// "init_images": [ // imageS!??!? wtf maybe for batch img2img?? i just dump one base64 in here
|
||||
// "string"
|
||||
// ],
|
||||
// "resize_mode": 0,
|
||||
// "denoising_strength": 0.75, // yeah see
|
||||
// "mask": "string", // string is just a base64 image
|
||||
// "mask_blur": 4,
|
||||
// "inpainting_fill": 0, // 0- fill, 1- orig, 2- latent noise, 3- latent nothing
|
||||
// "inpaint_full_res": true,
|
||||
// "inpaint_full_res_padding": 0, // px
|
||||
// "inpainting_mask_invert": 0, // bool??????? wtf
|
||||
// "include_init_images": false // ??????
|
||||
|
||||
}
|
||||
|
||||
// stuff things use
|
||||
var blockNewImages = false;
|
||||
var returnedImages;
|
||||
var imageIndex = 0;
|
||||
var tmpImgXYWH = {};
|
||||
var host = "";
|
||||
var url = "/sdapi/v1/";
|
||||
var endpoint = "txt2img"
|
||||
var frameX = 512;
|
||||
var frameY = 512;
|
||||
var prevMouseX = 0;
|
||||
var prevMouseY = 0;
|
||||
var mouseX = 0;
|
||||
var mouseY = 0;
|
||||
var canvasX = 0;
|
||||
var canvasY = 0;
|
||||
var snapX = 0;
|
||||
var snapY = 0;
|
||||
var drawThis = {};
|
||||
var clicked = false;
|
||||
const basePixelCount = 64; //64 px - ALWAYS 64 PX
|
||||
var scaleFactor = 8; //x64 px
|
||||
var snapToGrid = true; //yeah that's all it takes sure //TODO make this work somehow
|
||||
var paintMode = false;
|
||||
var eraseMode = false; //TODO this does absolutely nothing
|
||||
var backupMaskPaintCanvas; //???
|
||||
var backupMaskPaintCtx; //...?
|
||||
var backupMaskChunk = null;
|
||||
var backupMaskX = null;
|
||||
var backupMaskY = null;
|
||||
var sampler = "DDIM";
|
||||
var steps = 30;
|
||||
var cfgScale = 7.5;
|
||||
var totalImagesReturned;
|
||||
var batchSize = 2;
|
||||
var batchCount = 2;
|
||||
var maskBlur = 0;
|
||||
|
||||
var drawTargets = []; // is this needed? i only draw the last one anyway...
|
||||
|
||||
// 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");
|
||||
|
||||
// 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() {
|
||||
drawBackground();
|
||||
changeScaleFactor();
|
||||
changePaintMode();
|
||||
changeEraseMode();
|
||||
changeSampler();
|
||||
changeSteps();
|
||||
changeCfgScale();
|
||||
changeBatchCount();
|
||||
changeBatchSize();
|
||||
changeSnapMode();
|
||||
changeMaskBlur();
|
||||
document.getElementById("overlayCanvas").onmousemove = mouseMove;
|
||||
document.getElementById("overlayCanvas").onmousedown = mouseDown;
|
||||
document.getElementById("overlayCanvas").onmouseup = mouseUp;
|
||||
document.getElementById("scaleFactor").value = scaleFactor;
|
||||
}
|
||||
|
||||
function dream(x, y, prompt) {
|
||||
tmpImgXYWH.x = x;
|
||||
tmpImgXYWH.y = y;
|
||||
tmpImgXYWH.w = prompt.width;
|
||||
tmpImgXYWH.h = prompt.height;
|
||||
console.log("dreaming to " + host + url + endpoint + ":\r\n" + JSON.stringify(prompt));
|
||||
postData(prompt)
|
||||
.then((data) => {
|
||||
returnedImages = data.images;
|
||||
totalImagesReturned = data.images.length;
|
||||
blockNewImages = true;
|
||||
//console.log(data); // JSON data parsed by `data.json()` call
|
||||
imageAcceptReject(x, y, data);
|
||||
});
|
||||
}
|
||||
|
||||
async function postData(promptData) {
|
||||
this.host = document.getElementById("host").value;
|
||||
// Default options are marked with *
|
||||
const response = await fetch(this.host + this.url + this.endpoint, {
|
||||
method: 'POST', // *GET, POST, PUT, DELETE, etc.
|
||||
mode: 'cors', // no-cors, *cors, same-origin
|
||||
cache: 'default', // *default, no-cache, reload, force-cache, only-if-cached
|
||||
credentials: 'same-origin', // include, *same-origin, omit
|
||||
headers: {
|
||||
'Accept': 'application/json',
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
redirect: 'follow', // manual, *follow, error
|
||||
referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
|
||||
body: JSON.stringify(promptData) // body data type must match "Content-Type" header
|
||||
});
|
||||
return response.json(); // parses JSON response into native JavaScript objects
|
||||
}
|
||||
|
||||
function imageAcceptReject(x, y, data) {
|
||||
const img = new Image();
|
||||
img.onload = function () {
|
||||
tempCtx.drawImage(img, x, y); //imgCtx for actual image, tmp for... holding?
|
||||
var div = document.createElement("div");
|
||||
div.id = "veryTempDiv";
|
||||
div.style.position = "absolute";
|
||||
div.style.left = parseInt(x) + "px";
|
||||
div.style.top = parseInt(y + data.parameters.height) + "px";
|
||||
div.style.width = "150px";
|
||||
div.style.height = "50px";
|
||||
div.innerHTML = "<button onclick=\"prevImg(this)\"><</button><button onclick=\"nextImg(this)\">></button><span id=\"currentImgIndex\"></span> of <span id=\"totalImgIndex\"></span><button onclick=\"accept(this)\">Y</button><button onclick=\"reject(this)\">N</button>"
|
||||
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]; //TODO need way to dream batches and select from results
|
||||
}
|
||||
|
||||
function accept(evt) {
|
||||
// write image to imgcanvas
|
||||
clearBackupMask();
|
||||
placeImage();
|
||||
removeChoiceButtons();
|
||||
clearTargetMask();
|
||||
blockNewImages = false;
|
||||
}
|
||||
|
||||
function reject(evt) {
|
||||
// remove image entirely
|
||||
restoreBackupMask();
|
||||
clearBackupMask();
|
||||
clearTargetMask();
|
||||
removeChoiceButtons();
|
||||
blockNewImages = false;
|
||||
}
|
||||
|
||||
function prevImg(evt) {
|
||||
if (imageIndex == 0) {
|
||||
imageIndex = totalImagesReturned;
|
||||
}
|
||||
const img = new Image();
|
||||
tempCtx.clearRect(0, 0, tempCtx.width, tempCtx.height);
|
||||
img.onload = function () {
|
||||
tempCtx.drawImage(img, tmpImgXYWH.x, tmpImgXYWH.y); //imgCtx for actual image, tmp for... holding?
|
||||
}
|
||||
var tmpIndex = document.getElementById("currentImgIndex");
|
||||
imageIndex--;
|
||||
tmpIndex.innerText = imageIndex + 1;
|
||||
// load the image data after defining the closure
|
||||
img.src = "data:image/png;base64," + returnedImages[imageIndex]; //TODO need way to dream batches and select from results
|
||||
}
|
||||
|
||||
function nextImg(evt) {
|
||||
if (imageIndex == (totalImagesReturned - 1)) {
|
||||
imageIndex = -1;
|
||||
}
|
||||
const img = new Image();
|
||||
tempCtx.clearRect(0, 0, tempCtx.width, tempCtx.height);
|
||||
img.onload = function () {
|
||||
tempCtx.drawImage(img, tmpImgXYWH.x, tmpImgXYWH.y); //imgCtx for actual image, tmp for... holding?
|
||||
}
|
||||
var tmpIndex = document.getElementById("currentImgIndex");
|
||||
imageIndex++;
|
||||
tmpIndex.innerText = imageIndex + 1;
|
||||
// load the image data after defining the closure
|
||||
img.src = "data:image/png;base64," + returnedImages[imageIndex]; //TODO need way to dream batches and select from results
|
||||
}
|
||||
|
||||
function removeChoiceButtons(evt) {
|
||||
const element = document.getElementById("veryTempDiv");
|
||||
element.remove();
|
||||
tempCtx.clearRect(0, 0, tempCanvas.width, tempCanvas.height);
|
||||
}
|
||||
|
||||
function restoreBackupMask() {
|
||||
// reapply mask if exists
|
||||
if (backupMaskChunk != null && backupMaskX != null && backupMaskY != null) {
|
||||
// backup mask data exists
|
||||
var iData = new ImageData(backupMaskChunk.data, backupMaskChunk.height, backupMaskChunk.width);
|
||||
maskPaintCtx.putImageData(iData, backupMaskX, backupMaskY);
|
||||
}
|
||||
}
|
||||
|
||||
function clearBackupMask() {
|
||||
// clear backupmask
|
||||
backupMaskChunk = null;
|
||||
backupMaskX = null;
|
||||
backupMaskY = null;
|
||||
}
|
||||
|
||||
function clearTargetMask() {
|
||||
tgtCtx.clearRect(0, 0, tgtCanvas.width, tgtCanvas.height);
|
||||
}
|
||||
|
||||
function placeImage() {
|
||||
const img = new Image();
|
||||
img.onload = function () {
|
||||
imgCtx.drawImage(img, tmpImgXYWH.x, tmpImgXYWH.y);
|
||||
tmpImgXYWH = {};
|
||||
returnedImages = null;
|
||||
}
|
||||
//console.log(data.images[0]);
|
||||
|
||||
// load the image data after defining the closure
|
||||
img.src = "data:image/png;base64," + returnedImages[imageIndex]; //TODO need way to dream batches and select from results
|
||||
|
||||
}
|
||||
|
||||
function sleep(ms) {
|
||||
// what was this even for, anyway?
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
function snap(i) {
|
||||
// very cheap test proof of concept but it works surprisingly well
|
||||
var snapOffset = i % basePixelCount;
|
||||
if (snapOffset == 0) {
|
||||
return snapOffset;
|
||||
}
|
||||
return -snapOffset;
|
||||
}
|
||||
|
||||
|
||||
function mouseMove(evt) {
|
||||
const rect = ovCanvas.getBoundingClientRect() // not-quite pixel offset was driving me insane
|
||||
const canvasOffsetX = rect.left;
|
||||
const canvasOffsetY = rect.top;
|
||||
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);
|
||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height); // clear out the previous mouse cursor
|
||||
if (!paintMode) {
|
||||
// draw targeting square reticle thingy cursor
|
||||
ovCtx.strokeStyle = "#00000077";
|
||||
snapOffsetX = 0;
|
||||
snapOffsetY = 0;
|
||||
if (snapToGrid) {
|
||||
snapOffsetX = snap(canvasX);
|
||||
snapOffsetY = snap(canvasY);
|
||||
}
|
||||
finalX = snapOffsetX + canvasX;
|
||||
finalY = snapOffsetY + canvasY;
|
||||
ovCtx.strokeRect(parseInt(finalX - ((basePixelCount * scaleFactor) / 2)), parseInt(finalY - ((basePixelCount * scaleFactor) / 2)), basePixelCount * scaleFactor, basePixelCount * scaleFactor); //origin is middle of the frame
|
||||
} else {
|
||||
// draw big translucent red blob cursor
|
||||
ovCtx.beginPath();
|
||||
ovCtx.arc(canvasX, canvasY, 4 * scaleFactor, 0, 2 * Math.PI, true); // for some reason 4x on an arc is === to 8x on a line???
|
||||
ovCtx.fillStyle = "#FF6A6A50";
|
||||
ovCtx.fill();
|
||||
// in case i'm trying to draw
|
||||
mouseX = parseInt(evt.clientX - canvasOffsetX);
|
||||
mouseY = parseInt(evt.clientY - canvasOffsetY);
|
||||
if (clicked) {
|
||||
// i'm trying to draw, please draw :(
|
||||
if (eraseMode) {
|
||||
maskPaintCtx.globalCompositeOperation = 'destination-out';
|
||||
// maskPaintCtx.strokeStyle = "#FFFFFF00";
|
||||
} else {
|
||||
maskPaintCtx.globalCompositeOperation = 'source-over';
|
||||
maskPaintCtx.strokeStyle = "#FF6A6A10";
|
||||
}
|
||||
|
||||
maskPaintCtx.lineWidth = 8 * scaleFactor;
|
||||
maskPaintCtx.beginPath();
|
||||
maskPaintCtx.moveTo(prevMouseX, prevMouseY);
|
||||
maskPaintCtx.lineTo(mouseX, mouseY);
|
||||
maskPaintCtx.lineJoin = maskPaintCtx.lineCap = 'round';
|
||||
// console.log("moving from " + prevMouseX + ":" + prevMouseY + " to " + mouseX + ":" + mouseY);
|
||||
maskPaintCtx.stroke();
|
||||
}
|
||||
prevMouseX = mouseX;
|
||||
prevMouseY = mouseY;
|
||||
}
|
||||
}
|
||||
|
||||
function mouseDown(evt) {
|
||||
if (paintMode) {
|
||||
const rect = ovCanvas.getBoundingClientRect() // not-quite pixel offset was driving me insane
|
||||
const canvasOffsetX = rect.left;
|
||||
const canvasOffsetY = rect.top;
|
||||
prevMouseX = mouseX = evt.clientX - canvasOffsetX;
|
||||
prevMouseY = mouseY = evt.clientY - canvasOffsetY;
|
||||
clicked = true;
|
||||
} else {
|
||||
const rect = ovCanvas.getBoundingClientRect()
|
||||
var nextBox = {};
|
||||
nextBox.x = evt.clientX - ((basePixelCount * scaleFactor) / 2) - rect.left; //origin is middle of the frame
|
||||
nextBox.y = evt.clientY - ((basePixelCount * scaleFactor) / 2) - rect.top; //TODO make a way to set the origin to numpad dirs?
|
||||
nextBox.w = basePixelCount * scaleFactor;
|
||||
nextBox.h = basePixelCount * scaleFactor;
|
||||
drawTargets.push(nextBox);
|
||||
}
|
||||
}
|
||||
|
||||
function mouseUp(evt) {
|
||||
if (paintMode) {
|
||||
clicked = false;
|
||||
return;
|
||||
} else {
|
||||
if (!blockNewImages) {
|
||||
//TODO seriously, refactor this
|
||||
blockNewImages = true;
|
||||
clearTargetMask();
|
||||
tgtCtx.strokeStyle = "#55000077";
|
||||
var drawIt = {}; //why am i doing this????
|
||||
var target = drawTargets[drawTargets.length - 1]; //get the last one... why am i storing all of them?
|
||||
|
||||
snapOffsetX = 0;
|
||||
snapOffsetY = 0;
|
||||
if (snapToGrid) {
|
||||
snapOffsetX = snap(target.x);
|
||||
snapOffsetY = snap(target.y);
|
||||
}
|
||||
finalX = snapOffsetX + target.x;
|
||||
finalY = snapOffsetY + target.y;
|
||||
|
||||
drawThis.x = finalX;
|
||||
drawThis.y = finalY;
|
||||
drawThis.w = target.w;
|
||||
drawThis.h = target.h;
|
||||
tgtCtx.strokeRect(finalX, finalY, target.w, target.h);
|
||||
drawIt = drawThis; //TODO this is WRONG but also explicitly only draws the last image ... i think
|
||||
//check if there's image data already there
|
||||
// console.log(downX + ":" + downY + " :: " + this.isCanvasBlank(downX, downY));
|
||||
if (!isCanvasBlank(drawIt.x, drawIt.y, drawIt.w, drawIt.h, imgCanvas)) {
|
||||
// img2img
|
||||
var ctx = document.getElementById("canvas").getContext("2d");
|
||||
const imgChunk = ctx.getImageData(drawIt.x, drawIt.y, drawIt.w, drawIt.h);
|
||||
const imgChunkData = imgChunk.data;
|
||||
var canvas2 = document.getElementById("maskCanvas");
|
||||
var ctx2 = canvas2.getContext("2d");
|
||||
var canvas3 = document.getElementById("initImgCanvas");
|
||||
var ctx3 = canvas3.getContext("2d");
|
||||
// get blank pixels to use as mask
|
||||
const maskImgData = ctx2.createImageData(drawIt.w, drawIt.h);
|
||||
const initImgData = ctx2.createImageData(drawIt.w, drawIt.h);
|
||||
for (let i = 0; i < imgChunkData.length; i += 4) {
|
||||
// l->r, top->bottom, R G B A pixel values in a big ol array
|
||||
// make a simple mask
|
||||
if (imgChunkData[i + 3] == 0) { // rgba pixel values, 4th one is alpha, if it's 0 there's "nothing there" in the image display canvas and its time to outpaint
|
||||
maskImgData.data[i] = 255; // white mask gets painted over
|
||||
maskImgData.data[i + 1] = 255;
|
||||
maskImgData.data[i + 2] = 255;
|
||||
maskImgData.data[i + 3] = 255;
|
||||
initImgData.data[i] = 0; // null area on initial image becomes opaque black pixels
|
||||
initImgData.data[i + 1] = 0;
|
||||
initImgData.data[i + 2] = 0;
|
||||
initImgData.data[i + 3] = 255;
|
||||
} else { // leave these pixels alone
|
||||
maskImgData.data[i] = 0; // black mask gets ignored for in/outpainting
|
||||
maskImgData.data[i + 1] = 0;
|
||||
maskImgData.data[i + 2] = 0;
|
||||
maskImgData.data[i + 3] = 255; // but it still needs an opaque alpha channel
|
||||
initImgData.data[i] = imgChunkData[i]; // put the original picture back in the painted area
|
||||
initImgData.data[i + 1] = imgChunkData[i + 1];
|
||||
initImgData.data[i + 2] = imgChunkData[i + 2];
|
||||
initImgData.data[i + 3] = imgChunkData[i + 3]; //it's still RGBA so we can handily do this in nice chunks'o'4
|
||||
}
|
||||
}
|
||||
// also check for painted masks in region, add them as white pixels to mask canvas
|
||||
const maskChunk = maskPaintCtx.getImageData(drawIt.x, drawIt.y, drawIt.w, drawIt.h);
|
||||
const maskChunkData = maskChunk.data;
|
||||
for (let i = 0; i < maskChunkData.length; i += 4) {
|
||||
if (maskChunkData[i + 3] != 0) {
|
||||
maskImgData.data[i] = 255;
|
||||
maskImgData.data[i + 1] = 255;
|
||||
maskImgData.data[i + 2] = 255;
|
||||
maskImgData.data[i + 3] = 255;
|
||||
}
|
||||
}
|
||||
// backup any painted masks ingested then them, replacable if user doesn't like resultant image
|
||||
var clearArea = maskPaintCtx.createImageData(drawIt.w, drawIt.h);
|
||||
backupMaskChunk = maskChunk;
|
||||
backupMaskX = drawIt.x;
|
||||
backupMaskY = drawIt.y;
|
||||
|
||||
var clearD = clearArea.data;
|
||||
for (let i = 0; i < clearD.length; i++) {
|
||||
clearD[i] = 0; // just null it all out
|
||||
}
|
||||
maskPaintCtx.putImageData(clearArea, drawIt.x, drawIt.y);
|
||||
// mask monitors
|
||||
ctx2.putImageData(maskImgData, 0, 0);
|
||||
var maskBase64 = canvas2.toDataURL();
|
||||
ctx3.putImageData(initImgData, 0, 0);
|
||||
var initImgBase64 = canvas3.toDataURL();
|
||||
// img2img
|
||||
endpoint = "img2img";
|
||||
stableDiffusionData.mask = maskBase64;
|
||||
stableDiffusionData.init_images = [initImgBase64];
|
||||
// slightly more involved than txt2img
|
||||
} else {
|
||||
// txt2img
|
||||
endpoint = "txt2img";
|
||||
// easy enough
|
||||
}
|
||||
stableDiffusionData.prompt = document.getElementById("prompt").value;
|
||||
stableDiffusionData.negative_prompt = document.getElementById("negPrompt").value;
|
||||
stableDiffusionData.sampler_index = sampler;
|
||||
stableDiffusionData.steps = steps;
|
||||
stableDiffusionData.cfg_scale = cfgScale;
|
||||
stableDiffusionData.width = drawIt.w;
|
||||
stableDiffusionData.height = drawIt.h;
|
||||
stableDiffusionData.batch_size = batchSize;
|
||||
stableDiffusionData.n_iter = batchCount;
|
||||
stableDiffusionData.mask_blur = maskBlur;
|
||||
dream(drawIt.x, drawIt.y, stableDiffusionData);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function changeScaleFactor() {
|
||||
scaleFactor = document.getElementById("scaleFactor").value;
|
||||
document.getElementById("scaleFactorTxt").innerText = scaleFactor;
|
||||
}
|
||||
|
||||
function changeSteps() {
|
||||
steps = document.getElementById("steps").value;
|
||||
document.getElementById("stepsTxt").innerText = steps;
|
||||
}
|
||||
|
||||
function changePaintMode() {
|
||||
paintMode = document.getElementById("cbxPaint").checked;
|
||||
clearTargetMask();
|
||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||
}
|
||||
|
||||
function changeEraseMode() {
|
||||
eraseMode = document.getElementById("cbxErase").checked;
|
||||
clearTargetMask();
|
||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||
}
|
||||
|
||||
function changeSampler() {
|
||||
sampler = document.getElementById("samplerSelect").value;
|
||||
}
|
||||
|
||||
function changeCfgScale() {
|
||||
cfgScale = document.getElementById("cfgScale").value;
|
||||
document.getElementById("cfgScaleTxt").innerText = cfgScale;
|
||||
}
|
||||
|
||||
function changeBatchSize() {
|
||||
batchSize = document.getElementById("batchSize").value;
|
||||
document.getElementById("batchSizeText").innerText = batchSize;
|
||||
}
|
||||
|
||||
function changeBatchCount() {
|
||||
batchCount = document.getElementById("batchCount").value;
|
||||
document.getElementById("batchCountText").innerText = batchCount;
|
||||
}
|
||||
|
||||
function changeSnapMode() {
|
||||
snapToGrid = document.getElementById("cbxSnap").checked;
|
||||
}
|
||||
|
||||
function changeMaskBlur() {
|
||||
maskBlur = document.getElementById("maskBlur").value;
|
||||
}
|
||||
|
||||
function isCanvasBlank(x, y, w, h, specifiedCanvas) {
|
||||
var canvas = document.getElementById(specifiedCanvas.id);
|
||||
return !canvas.getContext('2d')
|
||||
.getImageData(x, y, w, h).data
|
||||
.some(channel => channel !== 0);
|
||||
}
|
||||
|
||||
function drawBackground() {
|
||||
bgCtx.lineWidth = 1;
|
||||
bgCtx.strokeStyle = '#999';
|
||||
var gridbox = bgCanvas.getBoundingClientRect();
|
||||
for (var i = 0; i < gridbox.width; i += 64) {
|
||||
bgCtx.moveTo(i, 0);
|
||||
bgCtx.lineTo(i, bgCanvas.height);
|
||||
bgCtx.stroke();
|
||||
}
|
||||
for (var i = 0; i < gridbox.height; i += 64) {
|
||||
bgCtx.moveTo(0, i);
|
||||
bgCtx.lineTo(gridbox.width, i);
|
||||
bgCtx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
function downloadImage() {
|
||||
var link = document.createElement('a');
|
||||
link.download = 'image.png';
|
||||
link.href = imgCanvas.toDataURL('image/png');
|
||||
link.click();
|
||||
}
|
Loading…
Reference in a new issue