Merge pull request #56 from zero01101/bleeding-edge

Allow mask inversion in both dream tools
This commit is contained in:
Victor Seiji Hariki 2022-11-26 23:21:57 -03:00 committed by GitHub
commit d91519226f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 295 additions and 96 deletions

View file

@ -30,52 +30,6 @@ body {
grid-row-gap: 5px;
}
.button-array {
display: flex;
justify-content: stretch;
}
.button-array > .button.tool {
flex: 1;
border-radius: 0;
}
.button-array > .button.tool:first-child {
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
.button-array > .button.tool:last-child {
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
.button.tool {
background-color: rgb(0, 0, 50);
color: rgb(255, 255, 255);
border-radius: 5px;
cursor: pointer;
border: none;
text-align: center;
outline: none;
font-size: 15px;
padding: 5px;
margin-top: 5px;
margin-bottom: 5px;
}
.button.tool:disabled {
background-color: #666 !important;
cursor: default;
}
.button.tool:hover {
background-color: rgb(30, 30, 80);
}
.button.tool:active {
background-color: rgb(60, 60, 130);
}
.collapsible {
background-color: rgb(0, 0, 0);
color: rgb(255, 255, 255);
@ -146,8 +100,29 @@ body {
position: absolute;
}
/* Mask colors for mask inversion */
/* Filters are some magic acquired at https://codepen.io/sosuke/pen/Pjoqqp */
.maskPaintCanvas {
filter: opacity(40%);
opacity: 0%;
}
.maskPaintCanvas.display {
opacity: 40%;
filter: invert(100%);
}
.maskPaintCanvas.display.opaque {
opacity: 100%;
}
.maskPaintCanvas.display.clear {
filter: invert(71%) sepia(46%) saturate(6615%) hue-rotate(321deg)
brightness(106%) contrast(100%);
}
.maskPaintCanvas.display.hold {
filter: invert(41%) sepia(16%) saturate(5181%) hue-rotate(218deg)
brightness(103%) contrast(108%);
}
.strokeText {
@ -243,3 +218,51 @@ div.prompt-wrapper > textarea {
div.prompt-wrapper > textarea:focus {
width: 700px;
}
/* Tool buttons */
.button-array {
display: flex;
justify-content: stretch;
}
.button-array > .button.tool {
flex: 1;
border-radius: 0;
}
.button-array > .button.tool:first-child {
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
.button-array > .button.tool:last-child {
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
.button-array > .button.tool {
background-color: rgb(0, 0, 50);
color: rgb(255, 255, 255);
cursor: pointer;
border: none;
text-align: center;
outline: none;
font-size: 15px;
padding: 5px;
margin-top: 5px;
margin-bottom: 5px;
}
.button-array > .button.tool:disabled {
background-color: #666 !important;
cursor: default;
}
.button-array > .button.tool:hover {
background-color: rgb(30, 30, 80);
}
.button-array > .button.tool:active,
.button.tool.active {
background-color: rgb(60, 60, 130);
}

View file

@ -50,21 +50,40 @@ const dream_generate_callback = (evn, state) => {
request.init_images = [auxCanvas.toDataURL()];
// Get mask image
auxCtx.fillStyle = "#000F";
auxCtx.fillRect(0, 0, bb.w, bb.h);
auxCtx.globalCompositeOperation = "destination-in";
auxCtx.drawImage(imgCanvas, bb.x, bb.y, bb.w, bb.h, 0, 0, bb.w, bb.h);
auxCtx.globalCompositeOperation = "destination-out";
auxCtx.drawImage(
maskPaintCanvas,
bb.x,
bb.y,
bb.w,
bb.h,
0,
0,
bb.w,
bb.h
);
if (state.invertMask) {
auxCtx.globalCompositeOperation = "destination-in";
auxCtx.drawImage(
maskPaintCanvas,
bb.x,
bb.y,
bb.w,
bb.h,
0,
0,
bb.w,
bb.h
);
auxCtx.globalCompositeOperation = "destination-in";
auxCtx.drawImage(imgCanvas, bb.x, bb.y, bb.w, bb.h, 0, 0, bb.w, bb.h);
} else {
auxCtx.globalCompositeOperation = "destination-in";
auxCtx.drawImage(imgCanvas, bb.x, bb.y, bb.w, bb.h, 0, 0, bb.w, bb.h);
auxCtx.globalCompositeOperation = "destination-out";
auxCtx.drawImage(
maskPaintCanvas,
bb.x,
bb.y,
bb.w,
bb.h,
0,
0,
bb.w,
bb.h
);
}
auxCtx.globalCompositeOperation = "destination-atop";
auxCtx.fillStyle = "#FFFF";
auxCtx.fillRect(0, 0, bb.w, bb.h);
@ -178,35 +197,37 @@ const dream_img2img_callback = (evn, state) => {
request.init_images = [auxCanvas.toDataURL()];
// Get mask image
auxCtx.fillStyle = state.invertMask ? "#FFFF" : "#000F";
auxCtx.fillRect(0, 0, bb.w, bb.h);
auxCtx.globalCompositeOperation = "destination-out";
auxCtx.drawImage(maskPaintCanvas, bb.x, bb.y, bb.w, bb.h, 0, 0, bb.w, bb.h);
auxCtx.globalCompositeOperation = "destination-atop";
auxCtx.fillStyle = state.invertMask ? "#000F" : "#FFFF";
auxCtx.fillRect(0, 0, bb.w, bb.h);
// Border Mask
if (state.borderMaskSize > 0) {
if (state.keepBorderSize > 0) {
auxCtx.globalCompositeOperation = "source-over";
auxCtx.fillStyle = "#000F";
auxCtx.fillRect(0, 0, state.borderMaskSize, bb.h);
auxCtx.fillRect(0, 0, bb.w, state.borderMaskSize);
auxCtx.fillRect(0, 0, state.keepBorderSize, bb.h);
auxCtx.fillRect(0, 0, bb.w, state.keepBorderSize);
auxCtx.fillRect(
bb.w - state.borderMaskSize,
bb.w - state.keepBorderSize,
0,
state.borderMaskSize,
state.keepBorderSize,
bb.h
);
auxCtx.fillRect(
0,
bb.h - state.borderMaskSize,
bb.h - state.keepBorderSize,
bb.w,
state.borderMaskSize
state.keepBorderSize
);
}
auxCtx.globalCompositeOperation = "destination-atop";
auxCtx.fillStyle = "#FFFF";
auxCtx.fillRect(0, 0, bb.w, bb.h);
request.mask = auxCanvas.toDataURL();
request.inpainting_mask_invert = true;
request.inpaint_full_res = state.fullResolution;
// Dream
dream(bb.x, bb.y, request, {method: "img2img", stopMarching, bb});
@ -232,16 +253,23 @@ const dreamTool = () =>
mouse.listen.canvas.onmousemove.on(state.mousemovecb);
mouse.listen.canvas.left.onclick.on(state.dreamcb);
mouse.listen.canvas.right.onclick.on(state.erasecb);
// Display Mask
setMask(state.invertMask ? "hold" : "clear");
},
(state, opt) => {
// Clear Listeners
mouse.listen.canvas.onmousemove.clear(state.mousemovecb);
mouse.listen.canvas.left.onclick.clear(state.dreamcb);
mouse.listen.canvas.right.onclick.clear(state.erasecb);
// Hide Mask
setMask("none");
},
{
init: (state) => {
state.snapToGrid = true;
state.invertMask = false;
state.overMaskPx = 0;
state.mousemovecb = (evn) => _reticle_draw(evn, state.snapToGrid);
state.dreamcb = (evn) => {
@ -252,11 +280,25 @@ const dreamTool = () =>
populateContextMenu: (menu, state) => {
if (!state.ctxmenu) {
state.ctxmenu = {};
// Snap to Grid Checkbox
state.ctxmenu.snapToGridLabel = _toolbar_input.checkbox(
state,
"snapToGrid",
"Snap To Grid"
).label;
// Invert Mask Checkbox
state.ctxmenu.invertMaskLabel = _toolbar_input.checkbox(
state,
"invertMask",
"Invert Mask",
() => {
setMask(state.invertMask ? "hold" : "clear");
}
).label;
// Overmasking Slider
state.ctxmenu.overMaskPxLabel = _toolbar_input.slider(
state,
"overMaskPx",
@ -269,6 +311,8 @@ const dreamTool = () =>
menu.appendChild(state.ctxmenu.snapToGridLabel);
menu.appendChild(document.createElement("br"));
menu.appendChild(state.ctxmenu.invertMaskLabel);
menu.appendChild(document.createElement("br"));
menu.appendChild(state.ctxmenu.overMaskPxLabel);
},
shortcut: "D",
@ -291,19 +335,28 @@ const img2imgTool = () =>
mouse.listen.canvas.onmousemove.on(state.mousemovecb);
mouse.listen.canvas.left.onclick.on(state.dreamcb);
mouse.listen.canvas.right.onclick.on(state.erasecb);
// Display Mask
setMask(state.invertMask ? "hold" : "clear");
},
(state, opt) => {
// Clear Listeners
mouse.listen.canvas.onmousemove.clear(state.mousemovecb);
mouse.listen.canvas.left.onclick.clear(state.dreamcb);
mouse.listen.canvas.right.onclick.clear(state.erasecb);
// Hide mask
setMask("none");
},
{
init: (state) => {
state.snapToGrid = true;
state.invertMask = true;
state.fullResolution = false;
state.denoisingStrength = 0.7;
state.borderMaskSize = 64;
state.keepBorderSize = 64;
state.mousemovecb = (evn) => {
_reticle_draw(evn, state.snapToGrid);
@ -322,21 +375,21 @@ const img2imgTool = () =>
auxCanvas.height = bb.h;
const auxCtx = auxCanvas.getContext("2d");
if (state.borderMaskSize > 0) {
auxCtx.fillStyle = "#FF6A6A50";
auxCtx.fillRect(0, 0, state.borderMaskSize, bb.h);
auxCtx.fillRect(0, 0, bb.w, state.borderMaskSize);
if (state.keepBorderSize > 0) {
auxCtx.fillStyle = "#6A6AFF7F";
auxCtx.fillRect(0, 0, state.keepBorderSize, bb.h);
auxCtx.fillRect(0, 0, bb.w, state.keepBorderSize);
auxCtx.fillRect(
bb.w - state.borderMaskSize,
bb.w - state.keepBorderSize,
0,
state.borderMaskSize,
state.keepBorderSize,
bb.h
);
auxCtx.fillRect(
0,
bb.h - state.borderMaskSize,
bb.h - state.keepBorderSize,
bb.w,
state.borderMaskSize
state.keepBorderSize
);
}
@ -361,6 +414,23 @@ const img2imgTool = () =>
"Snap To Grid"
).label;
// Invert Mask Checkbox
state.ctxmenu.invertMaskLabel = _toolbar_input.checkbox(
state,
"invertMask",
"Invert Mask",
() => {
setMask(state.invertMask ? "hold" : "clear");
}
).label;
// Inpaint Full Resolution Checkbox
state.ctxmenu.fullResolutionLabel = _toolbar_input.checkbox(
state,
"fullResolution",
"Inpaint Full Resolution"
).label;
// Denoising Strength Slider
state.ctxmenu.denoisingStrengthSlider = _toolbar_input.slider(
state,
@ -374,8 +444,8 @@ const img2imgTool = () =>
// Border Mask Size Slider
state.ctxmenu.borderMaskSlider = _toolbar_input.slider(
state,
"borderMaskSize",
"Border Mask Size",
"keepBorderSize",
"Keep Border Size",
0,
128,
1
@ -384,6 +454,10 @@ const img2imgTool = () =>
menu.appendChild(state.ctxmenu.snapToGridLabel);
menu.appendChild(document.createElement("br"));
menu.appendChild(state.ctxmenu.invertMaskLabel);
menu.appendChild(document.createElement("br"));
menu.appendChild(state.ctxmenu.fullResolutionLabel);
menu.appendChild(document.createElement("br"));
menu.appendChild(state.ctxmenu.denoisingStrengthSlider);
menu.appendChild(state.ctxmenu.borderMaskSlider);
},

View file

@ -1,11 +1,41 @@
const setMask = (state) => {
const canvas = document.querySelector("#maskPaintCanvas");
switch (state) {
case "clear":
canvas.classList.remove("hold");
canvas.classList.add("display", "clear");
break;
case "hold":
canvas.classList.remove("clear");
canvas.classList.add("display", "hold");
break;
case "neutral":
canvas.classList.remove("clear", "hold");
canvas.classList.add("display");
break;
case "none":
canvas.classList.remove("display", "hold", "clear");
break;
default:
console.debug(`Invalid mask type: ${state}`);
break;
}
};
const _mask_brush_draw_callback = (evn, state) => {
if (evn.initialTarget.id === "overlayCanvas") {
if (
(evn.initialTarget && evn.initialTarget.id === "overlayCanvas") ||
(!evn.initialTarget && evn.target.id === "overlayCanvas")
) {
maskPaintCtx.globalCompositeOperation = "source-over";
maskPaintCtx.strokeStyle = "#FF6A6A";
maskPaintCtx.strokeStyle = "black";
maskPaintCtx.lineWidth = state.brushSize;
maskPaintCtx.beginPath();
maskPaintCtx.moveTo(evn.px, evn.py);
maskPaintCtx.moveTo(
evn.px === undefined ? evn.x : evn.px,
evn.py === undefined ? evn.y : evn.py
);
maskPaintCtx.lineTo(evn.x, evn.y);
maskPaintCtx.lineJoin = maskPaintCtx.lineCap = "round";
maskPaintCtx.stroke();
@ -13,13 +43,19 @@ const _mask_brush_draw_callback = (evn, state) => {
};
const _mask_brush_erase_callback = (evn, state) => {
if (evn.initialTarget.id === "overlayCanvas") {
if (
(evn.initialTarget && evn.initialTarget.id === "overlayCanvas") ||
(!evn.initialTarget && evn.target.id === "overlayCanvas")
) {
maskPaintCtx.globalCompositeOperation = "destination-out";
maskPaintCtx.strokeStyle = "#FFFFFFFF";
maskPaintCtx.strokeStyle = "black";
maskPaintCtx.lineWidth = state.brushSize;
maskPaintCtx.beginPath();
maskPaintCtx.moveTo(evn.px, evn.py);
maskPaintCtx.moveTo(
evn.px === undefined ? evn.x : evn.px,
evn.py === undefined ? evn.y : evn.py
);
maskPaintCtx.lineTo(evn.x, evn.y);
maskPaintCtx.lineJoin = maskPaintCtx.lineCap = "round";
maskPaintCtx.stroke();
@ -38,15 +74,28 @@ const maskBrushTool = () =>
// Start Listeners
mouse.listen.canvas.onmousemove.on(state.movecb);
mouse.listen.canvas.onwheel.on(state.wheelcb);
mouse.listen.canvas.left.onpaintstart.on(state.drawcb);
mouse.listen.canvas.left.onpaint.on(state.drawcb);
mouse.listen.canvas.right.onpaintstart.on(state.erasecb);
mouse.listen.canvas.right.onpaint.on(state.erasecb);
// Display Mask
setMask("neutral");
},
(state, opt) => {
// Clear Listeners
mouse.listen.canvas.onmousemove.clear(state.movecb);
mouse.listen.canvas.onwheel.clear(state.wheelcb);
mouse.listen.canvas.left.onpaintstart.clear(state.drawcb);
mouse.listen.canvas.left.onpaint.clear(state.drawcb);
mouse.listen.canvas.right.onpaintstart.clear(state.erasecb);
mouse.listen.canvas.right.onpaint.clear(state.erasecb);
// Hide Mask
setMask("none");
state.ctxmenu.previewMaskButton.classList.remove("active");
maskPaintCanvas.classList.remove("opaque");
state.preview = false;
},
{
init: (state) => {
@ -63,13 +112,23 @@ const maskBrushTool = () =>
state.ctxmenu.brushSizeText.value = size;
};
state.preview = false;
state.movecb = (evn) => {
if (evn.target.id === "overlayCanvas") {
// draw big translucent red blob cursor
// draw big translucent white blob cursor
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.fillStyle = "#FF6A6A50";
ovCtx.fillStyle = "#FFFFFF50";
ovCtx.fill();
if (state.preview) {
ovCtx.strokeStyle = "#000F";
ovCtx.setLineDash([4, 2]);
ovCtx.stroke();
ovCtx.setLineDash([]);
}
}
};
@ -100,9 +159,49 @@ const maskBrushTool = () =>
);
state.ctxmenu.brushSizeSlider = brushSizeSlider.slider;
state.setBrushSize = brushSizeSlider.setValue;
// Some mask-related action buttons
const actionArray = document.createElement("div");
actionArray.classList.add("button-array");
const clearMaskButton = document.createElement("button");
clearMaskButton.classList.add("button", "tool");
clearMaskButton.textContent = "Clear";
clearMaskButton.title = "Clears Painted Mask";
clearMaskButton.onclick = () => {
maskPaintCtx.clearRect(
0,
0,
maskPaintCanvas.width,
maskPaintCanvas.height
);
};
const previewMaskButton = document.createElement("button");
previewMaskButton.classList.add("button", "tool");
previewMaskButton.textContent = "Preview";
previewMaskButton.title = "Displays Mask with Full Opacity";
previewMaskButton.onclick = () => {
if (previewMaskButton.classList.contains("active")) {
maskPaintCanvas.classList.remove("opaque");
state.preview = false;
} else {
maskPaintCanvas.classList.add("opaque");
state.preview = true;
}
previewMaskButton.classList.toggle("active");
};
state.ctxmenu.previewMaskButton = previewMaskButton;
actionArray.appendChild(clearMaskButton);
actionArray.appendChild(previewMaskButton);
state.ctxmenu.actionArray = actionArray;
}
menu.appendChild(state.ctxmenu.brushSizeSlider);
menu.appendChild(state.ctxmenu.actionArray);
},
shortcut: "M",
}

View file

@ -153,7 +153,7 @@ const stampTool = () =>
};
state.movecb = (evn) => {
if (evn.target.id === "overlayCanvas") {
if (evn.target && evn.target.id === "overlayCanvas") {
let x = evn.x;
let y = evn.y;
if (state.snapToGrid) {

View file

@ -132,13 +132,16 @@ const toolbar = {
* Premade inputs for populating the context menus
*/
const _toolbar_input = {
checkbox: (state, dataKey, text) => {
checkbox: (state, dataKey, text, cb = null) => {
if (state[dataKey] === undefined) state[dataKey] = false;
const checkbox = document.createElement("input");
checkbox.type = "checkbox";
checkbox.checked = state[dataKey];
checkbox.onchange = () => (state[dataKey] = checkbox.checked);
checkbox.onchange = () => {
state[dataKey] = checkbox.checked;
cb && cb();
};
const label = document.createElement("label");
label.appendChild(checkbox);

View file

@ -35,7 +35,7 @@ const guid = (size = 3) => {
.toString(16)
.substring(1);
};
// returns id of format 'aaaaaaaa'-'aaaa'-'aaaa'-'aaaa'-'aaaaaaaaaaaa'
// returns id of format 'aaaa'-'aaaa'-'aaaa' by default
let id = "";
for (var i = 0; i < size - 1; i++) id += s4() + "-";
id += s4();