layers?
Signed-off-by: Victor Seiji Hariki <victorseijih@gmail.com>
This commit is contained in:
parent
559f4493ca
commit
14df643326
20 changed files with 692 additions and 178 deletions
24
css/icons.css
Normal file
24
css/icons.css
Normal file
|
@ -0,0 +1,24 @@
|
||||||
|
.ui.icon > .icon-eye-off {
|
||||||
|
-webkit-mask-image: url("/res/icons/eye-off.svg");
|
||||||
|
mask-image: url("/res/icons/eye-off.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.icon > .icon-eye {
|
||||||
|
-webkit-mask-image: url("/res/icons/eye.svg");
|
||||||
|
mask-image: url("/res/icons/eye.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.icon > .icon-file-plus {
|
||||||
|
-webkit-mask-image: url("/res/icons/file-plus.svg");
|
||||||
|
mask-image: url("/res/icons/file-plus.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.icon > .icon-chevron-down {
|
||||||
|
-webkit-mask-image: url("/res/icons/chevron-down.svg");
|
||||||
|
mask-image: url("/res/icons/chevron-down.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.icon > .icon-chevron-up {
|
||||||
|
-webkit-mask-image: url("/res/icons/chevron-up.svg");
|
||||||
|
mask-image: url("/res/icons/chevron-up.svg");
|
||||||
|
}
|
|
@ -44,3 +44,21 @@
|
||||||
bottom: 0;
|
bottom: 0;
|
||||||
right: 0;
|
right: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#layer-overlay {
|
||||||
|
position: fixed;
|
||||||
|
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
z-index: 15;
|
||||||
|
}
|
||||||
|
|
||||||
|
#layer-render.pixelated canvas {
|
||||||
|
image-rendering: crisp-edges;
|
||||||
|
}
|
||||||
|
|
|
@ -113,3 +113,48 @@ select > option:checked::after {
|
||||||
mask-image: url("/res/icons/check.svg");
|
mask-image: url("/res/icons/check.svg");
|
||||||
mask-size: contain;
|
mask-size: contain;
|
||||||
}
|
}
|
||||||
|
/*************/
|
||||||
|
/* UI styles */
|
||||||
|
/*************/
|
||||||
|
|
||||||
|
/* The separator */
|
||||||
|
.ui.separator {
|
||||||
|
width: 80%;
|
||||||
|
margin: auto;
|
||||||
|
align-self: center;
|
||||||
|
border-top: 1px var(--c-hover) solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Icon button */
|
||||||
|
.ui.squaer {
|
||||||
|
aspect-ratio: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.button.icon {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.button.icon > *:first-child {
|
||||||
|
flex: 1;
|
||||||
|
margin: 3px;
|
||||||
|
|
||||||
|
mask-position: center;
|
||||||
|
|
||||||
|
-webkit-mask-size: contain;
|
||||||
|
mask-size: contain;
|
||||||
|
-webkit-mask-repeat: no-repeat;
|
||||||
|
mask-repeat: no-repeat;
|
||||||
|
background-color: var(--c-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.ui.button.icon:hover {
|
||||||
|
background-color: var(--c-hover);
|
||||||
|
}
|
||||||
|
|
|
@ -1,16 +1,132 @@
|
||||||
|
.layer-manager {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
|
||||||
|
border-radius: 5px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
background-color: var(--c-primary);
|
||||||
|
}
|
||||||
|
|
||||||
#layer-list {
|
#layer-list {
|
||||||
height: 200px;
|
height: 200px;
|
||||||
|
|
||||||
|
background-color: var(--c-primary);
|
||||||
|
|
||||||
|
border-top-left-radius: 5px;
|
||||||
|
border-top-right-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#layer-list > *:first-child {
|
||||||
|
border-top-left-radius: 5px;
|
||||||
|
border-top-right-radius: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
#layer-list .ui-layer {
|
#layer-list .ui-layer {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
height: 25px;
|
||||||
|
padding-left: 5px;
|
||||||
|
padding-right: 5px;
|
||||||
|
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: #fff3;
|
|
||||||
|
color: var(--c-text);
|
||||||
|
|
||||||
|
transition-duration: 50ms;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#layer-list .ui-layer.active {
|
||||||
|
background-color: var(--c-active);
|
||||||
|
}
|
||||||
|
#layer-list .ui-layer.active:hover,
|
||||||
#layer-list .ui-layer:hover {
|
#layer-list .ui-layer:hover {
|
||||||
filter: brightness(90%);
|
background-color: var(--c-hover);
|
||||||
|
}
|
||||||
|
#layer-list .ui-layer.active:active,
|
||||||
|
#layer-list .ui-layer:active {
|
||||||
|
background-color: var(--c-hover);
|
||||||
|
filter: brightness(120%);
|
||||||
}
|
}
|
||||||
|
|
||||||
#layer-list .ui-layer:active {
|
#layer-list .ui-layer > .title {
|
||||||
filter: brightness(80%);
|
flex: 1;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
background-color: transparent;
|
||||||
|
|
||||||
|
border: 0;
|
||||||
|
color: var(--c-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
#layer-list .ui-layer > .actions {
|
||||||
|
display: flex;
|
||||||
|
align-self: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
#layer-list .actions > button {
|
||||||
|
display: flex;
|
||||||
|
align-items: stretch;
|
||||||
|
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
width: 25px;
|
||||||
|
aspect-ratio: 1;
|
||||||
|
|
||||||
|
background-color: transparent;
|
||||||
|
border: 0;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
#layer-list .ui-layer > .actions > *:hover > * {
|
||||||
|
margin: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#layer-list .actions > button > *:first-child {
|
||||||
|
flex: 1;
|
||||||
|
margin: 3px;
|
||||||
|
|
||||||
|
-webkit-mask-size: contain;
|
||||||
|
mask-size: contain;
|
||||||
|
background-color: var(--c-text);
|
||||||
|
}
|
||||||
|
|
||||||
|
#layer-list .actions > .rename-btn > *:first-child {
|
||||||
|
-webkit-mask-image: url("/res/icons/edit.svg");
|
||||||
|
mask-image: url("/res/icons/edit.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
#layer-list .actions > .delete-btn > *:first-child {
|
||||||
|
-webkit-mask-image: url("/res/icons/trash.svg");
|
||||||
|
mask-image: url("/res/icons/trash.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
#layer-list .actions > .hide-btn > *:first-child {
|
||||||
|
-webkit-mask-image: url("/res/icons/eye.svg");
|
||||||
|
mask-image: url("/res/icons/eye.svg");
|
||||||
|
}
|
||||||
|
#layer-list .hidden .actions > .hide-btn > *:first-child {
|
||||||
|
-webkit-mask-image: url("/res/icons/eye-off.svg");
|
||||||
|
mask-image: url("/res/icons/eye-off.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-manager > .separator {
|
||||||
|
width: calc(100% - 10px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-manager > .layer-list-actions {
|
||||||
|
display: flex;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
justify-content: stretch;
|
||||||
|
}
|
||||||
|
|
||||||
|
.layer-manager > .layer-list-actions > * {
|
||||||
|
flex: 1;
|
||||||
|
height: 25px;
|
||||||
}
|
}
|
||||||
|
|
40
index.html
40
index.html
|
@ -5,6 +5,7 @@
|
||||||
<title>openOutpaint 🐠</title>
|
<title>openOutpaint 🐠</title>
|
||||||
<!-- CSS Variables -->
|
<!-- CSS Variables -->
|
||||||
<link href="css/colors.css" rel="stylesheet" />
|
<link href="css/colors.css" rel="stylesheet" />
|
||||||
|
<link href="css/icons.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/layers.css" rel="stylesheet" />
|
||||||
|
@ -110,6 +111,12 @@
|
||||||
step="1"
|
step="1"
|
||||||
onchange="changeMaskBlur()" />
|
onchange="changeMaskBlur()" />
|
||||||
<br />
|
<br />
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="cbxSmooth"
|
||||||
|
checked
|
||||||
|
onchange="changeSmoothRendering()" />
|
||||||
|
<label for="cbxSmooth">Smooth Rendering</label>
|
||||||
<!-- Save/load image section -->
|
<!-- Save/load image section -->
|
||||||
<button type="button" class="collapsible">Save/Upscaling</button>
|
<button type="button" class="collapsible">Save/Upscaling</button>
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
@ -202,7 +209,35 @@
|
||||||
style="right: 10px; bottom: 10px">
|
style="right: 10px; bottom: 10px">
|
||||||
<div class="draggable floating-window-title">Layers</div>
|
<div class="draggable floating-window-title">Layers</div>
|
||||||
<div class="menu-container" style="min-width: 200px">
|
<div class="menu-container" style="min-width: 200px">
|
||||||
<div id="layer-list" class="layer-list"></div>
|
<div class="layer-manager">
|
||||||
|
<div id="layer-list" class="layer-list"></div>
|
||||||
|
<div class="ui separator"></div>
|
||||||
|
<div class="layer-list-actions">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
title="Add Layer"
|
||||||
|
onclick="uil.addLayer(null, 'New Layer')"
|
||||||
|
class="ui icon button">
|
||||||
|
<div class="icon-file-plus"></div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
title="Move Layer Up"
|
||||||
|
onclick="uil.moveLayerUp()"
|
||||||
|
class="ui icon button">
|
||||||
|
<div class="icon-chevron-up"></div>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
title="Move Layer Down"
|
||||||
|
onclick="uil.moveLayerDown()"
|
||||||
|
class="ui icon button">
|
||||||
|
<div class="icon-chevron-down"></div>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -221,6 +256,9 @@
|
||||||
<!-- Canvases -->
|
<!-- Canvases -->
|
||||||
<div id="layer-render" class="layer-render-target"></div>
|
<div id="layer-render" class="layer-render-target"></div>
|
||||||
|
|
||||||
|
<!-- Overlay -->
|
||||||
|
<canvas id="layer-overlay" class="layer-overlay"></canvas>
|
||||||
|
|
||||||
<!-- Base Libs -->
|
<!-- Base Libs -->
|
||||||
<script src="js/lib/util.js" type="text/javascript"></script>
|
<script src="js/lib/util.js" type="text/javascript"></script>
|
||||||
<script src="js/lib/input.js" type="text/javascript"></script>
|
<script src="js/lib/input.js" type="text/javascript"></script>
|
||||||
|
|
27
js/index.js
27
js/index.js
|
@ -330,12 +330,14 @@ async function testHostConnection() {
|
||||||
|
|
||||||
function newImage(evt) {
|
function newImage(evt) {
|
||||||
clearPaintedMask();
|
clearPaintedMask();
|
||||||
clearBackupMask();
|
uil.layers.forEach(({layer}) => {
|
||||||
commands.runCommand("eraseImage", "Clear Canvas", {
|
commands.runCommand("eraseImage", "Clear Canvas", {
|
||||||
x: 0,
|
x: 0,
|
||||||
y: 0,
|
y: 0,
|
||||||
w: uiLayers.active.canvas.width,
|
w: layer.canvas.width,
|
||||||
h: uiLayers.active.canvas.height,
|
h: layer.canvas.height,
|
||||||
|
ctx: layer.ctx,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -484,9 +486,16 @@ function changeHiResFix() {
|
||||||
);
|
);
|
||||||
localStorage.setItem("enable_hr", stableDiffusionData.enable_hr);
|
localStorage.setItem("enable_hr", stableDiffusionData.enable_hr);
|
||||||
}
|
}
|
||||||
|
function changeSmoothRendering() {
|
||||||
|
const layers = document.getElementById("layer-render");
|
||||||
|
if (document.getElementById("cbxSmooth").checked) {
|
||||||
|
layers.classList.remove("pixelated");
|
||||||
|
} else {
|
||||||
|
layers.classList.add("pixelated");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function isCanvasBlank(x, y, w, h, specifiedCanvas) {
|
function isCanvasBlank(x, y, w, h, canvas) {
|
||||||
var canvas = document.getElementById(specifiedCanvas.id);
|
|
||||||
return !canvas
|
return !canvas
|
||||||
.getContext("2d")
|
.getContext("2d")
|
||||||
.getImageData(x, y, w, h)
|
.getImageData(x, y, w, h)
|
||||||
|
@ -765,7 +774,7 @@ async function upscaleAndDownload() {
|
||||||
// get cropped canvas, send it to upscaler, download result
|
// get cropped canvas, send it to upscaler, download result
|
||||||
var upscale_factor = 2; // TODO: make this a user input 1.x - 4.0 or something
|
var upscale_factor = 2; // TODO: make this a user input 1.x - 4.0 or something
|
||||||
var upscaler = document.getElementById("upscalers").value;
|
var upscaler = document.getElementById("upscalers").value;
|
||||||
var croppedCanvas = cropCanvas(uiLayers.active.canvas);
|
var croppedCanvas = cropCanvas(uil.canvas);
|
||||||
if (croppedCanvas != null) {
|
if (croppedCanvas != null) {
|
||||||
var upscaler = document.getElementById("upscalers").value;
|
var upscaler = document.getElementById("upscalers").value;
|
||||||
var url =
|
var url =
|
||||||
|
|
|
@ -37,35 +37,12 @@ const ovCtx = ovLayer.ctx;
|
||||||
const debugCanvas = debugLayer.canvas; // where mouse cursor renders
|
const debugCanvas = debugLayer.canvas; // where mouse cursor renders
|
||||||
const debugCtx = debugLayer.ctx;
|
const debugCtx = debugLayer.ctx;
|
||||||
|
|
||||||
/**
|
/* WIP: Most cursors shouldn't need a zoomable canvas */
|
||||||
* Function that returns a canvas with full visible information of a certain bounding box.
|
/** @type {HTMLCanvasElement} */
|
||||||
*
|
const uiCanvas = document.getElementById("layer-overlay"); // where mouse cursor renders
|
||||||
* For now, only the img is used.
|
uiCanvas.width = uiCanvas.clientWidth;
|
||||||
*
|
uiCanvas.height = uiCanvas.clientHeight;
|
||||||
* @param {BoundingBox} bb The bouding box to get visible data from
|
const uiCtx = uiCanvas.getContext("2d");
|
||||||
* @returns {HTMLCanvasElement} The canvas element containing visible image data
|
|
||||||
*/
|
|
||||||
const getVisible = (bb) => {
|
|
||||||
const canvas = document.createElement("canvas");
|
|
||||||
const ctx = canvas.getContext("2d");
|
|
||||||
|
|
||||||
canvas.width = bb.w;
|
|
||||||
canvas.height = bb.h;
|
|
||||||
ctx.drawImage(bgLayer.canvas, bb.x, bb.y, bb.w, bb.h, 0, 0, bb.w, bb.h);
|
|
||||||
ctx.drawImage(
|
|
||||||
uiLayers.active.canvas,
|
|
||||||
bb.x,
|
|
||||||
bb.y,
|
|
||||||
bb.w,
|
|
||||||
bb.h,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
bb.w,
|
|
||||||
bb.h
|
|
||||||
);
|
|
||||||
|
|
||||||
return canvas;
|
|
||||||
};
|
|
||||||
|
|
||||||
debugLayer.hide(); // Hidden by default
|
debugLayer.hide(); // Hidden by default
|
||||||
|
|
||||||
|
@ -141,6 +118,15 @@ const viewport = {
|
||||||
get h() {
|
get h() {
|
||||||
return (window.innerHeight * 1) / this.zoom;
|
return (window.innerHeight * 1) / this.zoom;
|
||||||
},
|
},
|
||||||
|
viewToCanvas(x, y) {
|
||||||
|
return {x, y};
|
||||||
|
},
|
||||||
|
canvasToView(x, y) {
|
||||||
|
return {
|
||||||
|
x: window.innerWidth * ((x - this.cx) / this.w) + window.innerWidth / 2,
|
||||||
|
y: window.innerHeight * ((y - this.cy) / this.h) + window.innerHeight / 2,
|
||||||
|
};
|
||||||
|
},
|
||||||
/**
|
/**
|
||||||
* Apply transformation
|
* Apply transformation
|
||||||
*
|
*
|
||||||
|
@ -220,4 +206,6 @@ mouse.listen.window.btn.middle.onpaintend.on((evn) => {
|
||||||
|
|
||||||
window.addEventListener("resize", () => {
|
window.addEventListener("resize", () => {
|
||||||
viewport.transform(imageCollection.element);
|
viewport.transform(imageCollection.element);
|
||||||
|
uiCanvas.width = uiCanvas.clientWidth;
|
||||||
|
uiCanvas.height = uiCanvas.clientHeight;
|
||||||
});
|
});
|
||||||
|
|
|
@ -192,7 +192,7 @@ commands.createCommand(
|
||||||
|
|
||||||
// Check if we have state
|
// Check if we have state
|
||||||
if (!state.context) {
|
if (!state.context) {
|
||||||
const context = options.ctx || uiLayers.active.ctx;
|
const context = options.ctx || uil.ctx;
|
||||||
state.context = context;
|
state.context = context;
|
||||||
|
|
||||||
// Saving what was in the canvas before the command
|
// Saving what was in the canvas before the command
|
||||||
|
@ -252,7 +252,7 @@ commands.createCommand(
|
||||||
|
|
||||||
// Check if we have state
|
// Check if we have state
|
||||||
if (!state.context) {
|
if (!state.context) {
|
||||||
const context = options.ctx || uiLayers.active.ctx;
|
const context = options.ctx || uil.ctx;
|
||||||
state.context = context;
|
state.context = context;
|
||||||
|
|
||||||
// Saving what was in the canvas before the command
|
// Saving what was in the canvas before the command
|
||||||
|
|
|
@ -250,14 +250,20 @@ function cropCanvas(sourceCanvas, options = {}) {
|
||||||
*
|
*
|
||||||
* @param {Object} options - Optional Information
|
* @param {Object} options - Optional Information
|
||||||
* @param {boolean} [options.cropToContent] - If we wish to crop to content first (default: true)
|
* @param {boolean} [options.cropToContent] - If we wish to crop to content first (default: true)
|
||||||
* @param {HTMLCanvasElement} [options.canvas] - The source canvas (default: uiLayers.active.canvas)
|
* @param {HTMLCanvasElement} [options.canvas] - The source canvas (default: visible)
|
||||||
* @param {string} [options.filename] - The filename to save as (default: '[ISO date] [Hours] [Minutes] [Seconds] openOutpaint image.png').\
|
* @param {string} [options.filename] - The filename to save as (default: '[ISO date] [Hours] [Minutes] [Seconds] openOutpaint image.png').\
|
||||||
* If null, opens image in new tab.
|
* If null, opens image in new tab.
|
||||||
*/
|
*/
|
||||||
function downloadCanvas(options = {}) {
|
function downloadCanvas(options = {}) {
|
||||||
|
console.debug(imageCollection);
|
||||||
defaultOpt(options, {
|
defaultOpt(options, {
|
||||||
cropToContent: true,
|
cropToContent: true,
|
||||||
canvas: uiLayers.active.canvas,
|
canvas: uil.getVisible({
|
||||||
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
w: imageCollection.size.w,
|
||||||
|
h: imageCollection.size.h,
|
||||||
|
}),
|
||||||
filename:
|
filename:
|
||||||
new Date()
|
new Date()
|
||||||
.toISOString()
|
.toISOString()
|
||||||
|
|
|
@ -2,32 +2,139 @@
|
||||||
* The layering UI window
|
* The layering UI window
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const uiLayers = {
|
const uil = {
|
||||||
|
_ui_layer_list: document.getElementById("layer-list"),
|
||||||
layers: [],
|
layers: [],
|
||||||
active: null,
|
_active: null,
|
||||||
|
set active(v) {
|
||||||
|
Array.from(this._ui_layer_list.children).forEach((child) => {
|
||||||
|
child.classList.remove("active");
|
||||||
|
});
|
||||||
|
|
||||||
|
v.entry.classList.add("active");
|
||||||
|
|
||||||
|
this._active = v;
|
||||||
|
},
|
||||||
|
get active() {
|
||||||
|
return this._active;
|
||||||
|
},
|
||||||
|
|
||||||
|
get layer() {
|
||||||
|
return this.active && this.active.layer;
|
||||||
|
},
|
||||||
|
|
||||||
|
get canvas() {
|
||||||
|
return this.layer && this.active.layer.canvas;
|
||||||
|
},
|
||||||
|
|
||||||
|
get ctx() {
|
||||||
|
return this.layer && this.active.layer.ctx;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronizes layer array to DOM
|
||||||
|
*/
|
||||||
_syncLayers() {
|
_syncLayers() {
|
||||||
const layersEl = document.getElementById("layer-list");
|
const layersEl = document.getElementById("layer-list");
|
||||||
|
|
||||||
const children = Array.from(layersEl.children);
|
const copy = this.layers.map((i) => i);
|
||||||
|
copy.reverse();
|
||||||
|
|
||||||
this.layers.forEach((uiLayer) => {
|
copy.forEach((uiLayer, index) => {
|
||||||
|
// If we have the correct layer here, then do nothing
|
||||||
|
if (
|
||||||
|
layersEl.children[index] &&
|
||||||
|
layersEl.children[index].id === `ui-layer-${uiLayer.id}`
|
||||||
|
)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// If the layer we are processing does not exist, then create it and add before current element
|
||||||
if (!uiLayer.entry) {
|
if (!uiLayer.entry) {
|
||||||
uiLayer.entry = document.createElement("div");
|
uiLayer.entry = document.createElement("div");
|
||||||
uiLayer.entry.textContent = uiLayer.name;
|
|
||||||
|
|
||||||
uiLayer.entry.id = `ui-layer-${uiLayer.id}`;
|
uiLayer.entry.id = `ui-layer-${uiLayer.id}`;
|
||||||
uiLayer.entry.classList.add("ui-layer");
|
uiLayer.entry.classList.add("ui-layer");
|
||||||
uiLayer.entry.addEventListener(
|
uiLayer.entry.addEventListener("click", () => {
|
||||||
"click",
|
this.active = uiLayer;
|
||||||
() => (this.active = uiLayer.layer)
|
});
|
||||||
);
|
|
||||||
|
|
||||||
if (true || children.length === 0) layersEl.appendChild(uiLayer.entry);
|
// Title Element
|
||||||
|
const titleEl = document.createElement("input");
|
||||||
|
titleEl.classList.add("title");
|
||||||
|
titleEl.value = uiLayer.name;
|
||||||
|
titleEl.style.pointerEvents = "none";
|
||||||
|
|
||||||
|
const deselect = () => {
|
||||||
|
titleEl.style.pointerEvents = "none";
|
||||||
|
titleEl.setSelectionRange(0, 0);
|
||||||
|
};
|
||||||
|
|
||||||
|
titleEl.addEventListener("blur", deselect);
|
||||||
|
uiLayer.entry.appendChild(titleEl);
|
||||||
|
|
||||||
|
uiLayer.entry.addEventListener("change", () => {
|
||||||
|
const name = titleEl.value.trim();
|
||||||
|
titleEl.value = name;
|
||||||
|
uiLayer.entry.title = name;
|
||||||
|
|
||||||
|
uiLayer.name = name;
|
||||||
|
|
||||||
|
this._syncLayers();
|
||||||
|
|
||||||
|
titleEl.blur();
|
||||||
|
});
|
||||||
|
uiLayer.entry.addEventListener("dblclick", () => {
|
||||||
|
titleEl.style.pointerEvents = "auto";
|
||||||
|
titleEl.focus();
|
||||||
|
titleEl.select();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Add action buttons
|
||||||
|
const actionArray = document.createElement("div");
|
||||||
|
actionArray.classList.add("actions");
|
||||||
|
|
||||||
|
const hideButton = document.createElement("button");
|
||||||
|
hideButton.addEventListener(
|
||||||
|
"click",
|
||||||
|
(evn) => {
|
||||||
|
evn.stopPropagation();
|
||||||
|
uiLayer.hidden = !uiLayer.hidden;
|
||||||
|
if (uiLayer.hidden) {
|
||||||
|
uiLayer.entry.classList.add("hidden");
|
||||||
|
} else uiLayer.entry.classList.remove("hidden");
|
||||||
|
},
|
||||||
|
{passive: false}
|
||||||
|
);
|
||||||
|
hideButton.title = "Hide/Unhide Layer";
|
||||||
|
hideButton.appendChild(document.createElement("div"));
|
||||||
|
hideButton.classList.add("hide-btn");
|
||||||
|
|
||||||
|
actionArray.appendChild(hideButton);
|
||||||
|
uiLayer.entry.appendChild(actionArray);
|
||||||
|
|
||||||
|
if (layersEl.children[index])
|
||||||
|
layersEl.children[index].before(uiLayer.entry);
|
||||||
|
else layersEl.appendChild(uiLayer.entry);
|
||||||
}
|
}
|
||||||
|
// If the layer already exists, just move it here
|
||||||
|
else {
|
||||||
|
layersEl.children[index].before(uiLayer.entry);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Synchronizes with the layer lib
|
||||||
|
this.layers.forEach((uiLayer, index) => {
|
||||||
|
if (index === 0) uiLayer.layer.moveAfter(bgLayer);
|
||||||
|
else uiLayer.layer.moveAfter(copy[index - 1].layer);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a user-manageable layer for image editing.
|
||||||
|
*
|
||||||
|
* @param {string} group The group the layer belongs to. [does nothing for now]
|
||||||
|
* @param {string} name The name of the new layer.
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
addLayer(group, name) {
|
addLayer(group, name) {
|
||||||
const layer = imageCollection.registerLayer(null, {
|
const layer = imageCollection.registerLayer(null, {
|
||||||
name,
|
name,
|
||||||
|
@ -40,17 +147,116 @@ const uiLayers = {
|
||||||
id: layer.id,
|
id: layer.id,
|
||||||
group,
|
group,
|
||||||
name,
|
name,
|
||||||
|
_hidden: false,
|
||||||
|
set hidden(v) {
|
||||||
|
if (v) {
|
||||||
|
this._hidden = true;
|
||||||
|
this.layer.hide(v);
|
||||||
|
} else {
|
||||||
|
this._hidden = false;
|
||||||
|
this.layer.unhide(v);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
get hidden() {
|
||||||
|
return this._hidden;
|
||||||
|
},
|
||||||
entry: null,
|
entry: null,
|
||||||
layer,
|
layer,
|
||||||
};
|
};
|
||||||
this.layers.push(uiLayer);
|
this.layers.push(uiLayer);
|
||||||
|
|
||||||
this.active = uiLayer.layer;
|
|
||||||
|
|
||||||
this._syncLayers();
|
this._syncLayers();
|
||||||
|
|
||||||
|
this.active = uiLayer;
|
||||||
|
|
||||||
return uiLayer;
|
return uiLayer;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Moves a layer to a specified position
|
||||||
|
*
|
||||||
|
* @param {UserLayer} layer Layer to move
|
||||||
|
* @param {number} position Position to move the layer to
|
||||||
|
*/
|
||||||
|
moveLayerTo(layer, position) {
|
||||||
|
if (position < 0 || position >= this.layers.length)
|
||||||
|
throw new RangeError("Position out of bounds");
|
||||||
|
|
||||||
|
const index = this.layers.indexOf(layer);
|
||||||
|
if (index !== -1) {
|
||||||
|
if (this.layers.length < 2) return; // Do nothing if moving a layer doesn't make sense
|
||||||
|
|
||||||
|
this.layers.splice(index, 1);
|
||||||
|
this.layers.splice(position, 0, layer);
|
||||||
|
|
||||||
|
this._syncLayers();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
throw new ReferenceError("Layer could not be found");
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Moves a layer up a single position
|
||||||
|
*
|
||||||
|
* @param {UserLayer} [layer=uil.active] Layer to move
|
||||||
|
*/
|
||||||
|
moveLayerUp(layer = uil.active) {
|
||||||
|
const index = this.layers.indexOf(layer);
|
||||||
|
if (index === -1) throw new ReferenceError("Layer could not be found");
|
||||||
|
try {
|
||||||
|
this.moveLayerTo(layer, index + 1);
|
||||||
|
} catch (e) {}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Moves a layer down a single position
|
||||||
|
*
|
||||||
|
* @param {UserLayer} [layer=uil.active] Layer to move
|
||||||
|
*/
|
||||||
|
moveLayerDown(layer = uil.active) {
|
||||||
|
const index = this.layers.indexOf(layer);
|
||||||
|
if (index === -1) throw new ReferenceError("Layer could not be found");
|
||||||
|
try {
|
||||||
|
this.moveLayerTo(layer, index - 1);
|
||||||
|
} catch (e) {}
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Function that returns a canvas with full visible information of a certain bounding box.
|
||||||
|
*
|
||||||
|
* For now, only the img is used.
|
||||||
|
*
|
||||||
|
* @param {BoundingBox} bb The bouding box to get visible data from
|
||||||
|
* @param {object} [options] Options
|
||||||
|
* @param {boolean} [options.includeBg=false] Whether to include the background
|
||||||
|
* @returns {HTMLCanvasElement} The canvas element containing visible image data
|
||||||
|
*/
|
||||||
|
getVisible(bb, options = {}) {
|
||||||
|
defaultOpt(options, {
|
||||||
|
includeBg: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
|
canvas.width = bb.w;
|
||||||
|
canvas.height = bb.h;
|
||||||
|
if (options.includeBg)
|
||||||
|
ctx.drawImage(bgLayer.canvas, bb.x, bb.y, bb.w, bb.h, 0, 0, bb.w, bb.h);
|
||||||
|
this.layers.forEach((layer) => {
|
||||||
|
if (!layer.hidden)
|
||||||
|
ctx.drawImage(
|
||||||
|
layer.layer.canvas,
|
||||||
|
bb.x,
|
||||||
|
bb.y,
|
||||||
|
bb.w,
|
||||||
|
bb.h,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
bb.w,
|
||||||
|
bb.h
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
return canvas;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
uiLayers.addLayer(null, "Default Image Layer");
|
uil.addLayer(null, "Default Image Layer");
|
||||||
uiLayers.addLayer(null, "Test Extra Layer");
|
|
||||||
|
|
|
@ -35,8 +35,14 @@ const colorBrushTool = () =>
|
||||||
"Color Brush",
|
"Color Brush",
|
||||||
(state, opt) => {
|
(state, opt) => {
|
||||||
// Draw new cursor immediately
|
// Draw new cursor immediately
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
uiCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||||
state.movecb({...mouse.coords.world.pos});
|
state.movecb({
|
||||||
|
...mouse.coords.world.pos,
|
||||||
|
evn: {
|
||||||
|
clientX: mouse.coords.window.pos.x,
|
||||||
|
clientY: mouse.coords.window.pos.y,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// Layer for eyedropper magnifiying glass
|
// Layer for eyedropper magnifiying glass
|
||||||
state.glassLayer = imageCollection.registerLayer(null, {
|
state.glassLayer = imageCollection.registerLayer(null, {
|
||||||
|
@ -106,6 +112,8 @@ const colorBrushTool = () =>
|
||||||
// Cancel any eyedropping
|
// Cancel any eyedropping
|
||||||
state.drawing = false;
|
state.drawing = false;
|
||||||
state.disableDropper();
|
state.disableDropper();
|
||||||
|
|
||||||
|
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
init: (state) => {
|
init: (state) => {
|
||||||
|
@ -146,26 +154,43 @@ const colorBrushTool = () =>
|
||||||
state.movecb = (evn) => {
|
state.movecb = (evn) => {
|
||||||
lastMouseMoveEvn = evn;
|
lastMouseMoveEvn = evn;
|
||||||
|
|
||||||
// draw drawing cursor
|
const vcp = {x: evn.evn.clientX, y: evn.evn.clientY};
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
|
||||||
|
|
||||||
|
// draw drawing cursor
|
||||||
|
uiCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||||
|
|
||||||
|
uiCtx.beginPath();
|
||||||
|
uiCtx.arc(
|
||||||
|
vcp.x,
|
||||||
|
vcp.y,
|
||||||
|
state.eyedropper ? 50 : (state.brushSize / 2) * viewport.zoom,
|
||||||
|
0,
|
||||||
|
2 * Math.PI,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
uiCtx.strokeStyle = "black";
|
||||||
|
uiCtx.stroke();
|
||||||
|
|
||||||
|
// Draw eyedropper cursor and magnifiying glass
|
||||||
if (state.eyedropper) {
|
if (state.eyedropper) {
|
||||||
const bb = getBoundingBox(evn.x, evn.y, 7, 7, false);
|
const bb = getBoundingBox(evn.x, evn.y, 7, 7, false);
|
||||||
|
|
||||||
const canvas = getVisible(bb);
|
const canvas = uil.getVisible(bb, {includeBg: true});
|
||||||
state.glassLayer.ctx.clearRect(0, 0, 7, 7);
|
state.glassLayer.ctx.clearRect(0, 0, 7, 7);
|
||||||
state.glassLayer.ctx.drawImage(canvas, 0, 0);
|
state.glassLayer.ctx.drawImage(canvas, 0, 0);
|
||||||
state.glassLayer.moveTo(evn.x - 50, evn.y - 50);
|
state.glassLayer.moveTo(evn.x - 50, evn.y - 50);
|
||||||
|
|
||||||
ovCtx.beginPath();
|
|
||||||
ovCtx.arc(evn.x, evn.y, 50, 0, 2 * Math.PI, true); // for some reason 4x on an arc is === to 7x on a line???
|
|
||||||
ovCtx.strokeStyle = "black";
|
|
||||||
ovCtx.stroke();
|
|
||||||
} else {
|
} else {
|
||||||
ovCtx.beginPath();
|
uiCtx.beginPath();
|
||||||
ovCtx.arc(evn.x, evn.y, state.brushSize / 2, 0, 2 * Math.PI, true); // for some reason 4x on an arc is === to 7x on a line???
|
uiCtx.arc(
|
||||||
ovCtx.fillStyle = state.color + "50";
|
vcp.x,
|
||||||
ovCtx.fill();
|
vcp.y,
|
||||||
|
(state.brushSize / 2) * viewport.zoom,
|
||||||
|
0,
|
||||||
|
2 * Math.PI,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
uiCtx.fillStyle = state.color + "50";
|
||||||
|
uiCtx.fill();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -175,7 +200,7 @@ const colorBrushTool = () =>
|
||||||
state.brushSize -
|
state.brushSize -
|
||||||
Math.floor(state.config.brushScrollSpeed * evn.delta)
|
Math.floor(state.config.brushScrollSpeed * evn.delta)
|
||||||
);
|
);
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
uiCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||||
state.movecb(evn);
|
state.movecb(evn);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -271,20 +296,20 @@ const colorBrushTool = () =>
|
||||||
const bkpcanvas = state.eraseBackup.canvas;
|
const bkpcanvas = state.eraseBackup.canvas;
|
||||||
const bkpctx = state.eraseBackup.ctx;
|
const bkpctx = state.eraseBackup.ctx;
|
||||||
bkpctx.clearRect(0, 0, bkpcanvas.width, bkpcanvas.height);
|
bkpctx.clearRect(0, 0, bkpcanvas.width, bkpcanvas.height);
|
||||||
bkpctx.drawImage(uiLayers.active.canvas, 0, 0);
|
bkpctx.drawImage(uil.canvas, 0, 0);
|
||||||
|
|
||||||
uiLayers.active.ctx.globalCompositeOperation = "destination-out";
|
uil.ctx.globalCompositeOperation = "destination-out";
|
||||||
_color_brush_erase_callback(evn, state, uiLayers.active.ctx);
|
_color_brush_erase_callback(evn, state, uil.ctx);
|
||||||
uiLayers.active.ctx.globalCompositeOperation = "source-over";
|
uil.ctx.globalCompositeOperation = "source-over";
|
||||||
_color_brush_erase_callback(evn, state, state.eraseLayer.ctx);
|
_color_brush_erase_callback(evn, state, state.eraseLayer.ctx);
|
||||||
};
|
};
|
||||||
|
|
||||||
state.erasecb = (evn) => {
|
state.erasecb = (evn) => {
|
||||||
if (state.eyedropper || !state.erasing) return;
|
if (state.eyedropper || !state.erasing) return;
|
||||||
if (state.affectMask) _mask_brush_erase_callback(evn, state);
|
if (state.affectMask) _mask_brush_erase_callback(evn, state);
|
||||||
uiLayers.active.ctx.globalCompositeOperation = "destination-out";
|
uil.ctx.globalCompositeOperation = "destination-out";
|
||||||
_color_brush_erase_callback(evn, state, uiLayers.active.ctx);
|
_color_brush_erase_callback(evn, state, uil.ctx);
|
||||||
uiLayers.active.ctx.globalCompositeOperation = "source-over";
|
uil.ctx.globalCompositeOperation = "source-over";
|
||||||
_color_brush_erase_callback(evn, state, state.eraseLayer.ctx);
|
_color_brush_erase_callback(evn, state, state.eraseLayer.ctx);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -300,13 +325,8 @@ const colorBrushTool = () =>
|
||||||
const cropped = cropCanvas(canvas, {border: 10});
|
const cropped = cropCanvas(canvas, {border: 10});
|
||||||
const bb = cropped.bb;
|
const bb = cropped.bb;
|
||||||
|
|
||||||
uiLayers.active.ctx.clearRect(
|
uil.ctx.clearRect(0, 0, uil.canvas.width, uil.canvas.height);
|
||||||
0,
|
uil.ctx.drawImage(bkpcanvas, 0, 0);
|
||||||
0,
|
|
||||||
uiLayers.active.canvas.width,
|
|
||||||
uiLayers.active.canvas.height
|
|
||||||
);
|
|
||||||
uiLayers.active.ctx.drawImage(bkpcanvas, 0, 0);
|
|
||||||
|
|
||||||
commands.runCommand("eraseImage", "Color Brush Erase", {
|
commands.runCommand("eraseImage", "Color Brush Erase", {
|
||||||
mask: cropped.canvas,
|
mask: cropped.canvas,
|
||||||
|
|
|
@ -366,8 +366,12 @@ const dream_generate_callback = async (evn, state) => {
|
||||||
// Don't allow another image until is finished
|
// Don't allow another image until is finished
|
||||||
blockNewImages = true;
|
blockNewImages = true;
|
||||||
|
|
||||||
|
// Get visible pixels
|
||||||
|
const visibleCanvas = uil.getVisible(bb);
|
||||||
|
console.debug(visibleCanvas);
|
||||||
|
|
||||||
// Use txt2img if canvas is blank
|
// Use txt2img if canvas is blank
|
||||||
if (isCanvasBlank(bb.x, bb.y, bb.w, bb.h, uiLayers.active.canvas)) {
|
if (isCanvasBlank(0, 0, bb.w, bb.h, visibleCanvas)) {
|
||||||
// Dream
|
// Dream
|
||||||
_generate("txt2img", request, bb);
|
_generate("txt2img", request, bb);
|
||||||
} else {
|
} else {
|
||||||
|
@ -384,9 +388,9 @@ const dream_generate_callback = async (evn, state) => {
|
||||||
// Get init image
|
// Get init image
|
||||||
auxCtx.fillRect(0, 0, request.width, request.height);
|
auxCtx.fillRect(0, 0, request.width, request.height);
|
||||||
auxCtx.drawImage(
|
auxCtx.drawImage(
|
||||||
uiLayers.active.canvas,
|
visibleCanvas,
|
||||||
bb.x,
|
0,
|
||||||
bb.y,
|
0,
|
||||||
bb.w,
|
bb.w,
|
||||||
bb.h,
|
bb.h,
|
||||||
0,
|
0,
|
||||||
|
@ -417,9 +421,9 @@ const dream_generate_callback = async (evn, state) => {
|
||||||
|
|
||||||
auxCtx.globalCompositeOperation = "destination-in";
|
auxCtx.globalCompositeOperation = "destination-in";
|
||||||
auxCtx.drawImage(
|
auxCtx.drawImage(
|
||||||
uiLayers.active.canvas,
|
visibleCanvas,
|
||||||
bb.x,
|
0,
|
||||||
bb.y,
|
0,
|
||||||
bb.w,
|
bb.w,
|
||||||
bb.h,
|
bb.h,
|
||||||
0,
|
0,
|
||||||
|
@ -430,9 +434,9 @@ const dream_generate_callback = async (evn, state) => {
|
||||||
} else {
|
} else {
|
||||||
auxCtx.globalCompositeOperation = "destination-in";
|
auxCtx.globalCompositeOperation = "destination-in";
|
||||||
auxCtx.drawImage(
|
auxCtx.drawImage(
|
||||||
uiLayers.active.canvas,
|
visibleCanvas,
|
||||||
bb.x,
|
0,
|
||||||
bb.y,
|
0,
|
||||||
bb.w,
|
bb.w,
|
||||||
bb.h,
|
bb.h,
|
||||||
0,
|
0,
|
||||||
|
@ -535,8 +539,13 @@ const dream_img2img_callback = (evn, state) => {
|
||||||
state.snapToGrid && basePixelCount
|
state.snapToGrid && basePixelCount
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Get visible pixels
|
||||||
|
const visibleCanvas = uil.getVisible(bb);
|
||||||
|
|
||||||
|
console.debug(visibleCanvas);
|
||||||
|
|
||||||
// Do nothing if no image exists
|
// Do nothing if no image exists
|
||||||
if (isCanvasBlank(bb.x, bb.y, bb.w, bb.h, uiLayers.active.canvas)) return;
|
if (isCanvasBlank(0, 0, bb.w, bb.h, visibleCanvas)) return;
|
||||||
|
|
||||||
// Build request to the API
|
// Build request to the API
|
||||||
const request = {};
|
const request = {};
|
||||||
|
@ -565,9 +574,9 @@ const dream_img2img_callback = (evn, state) => {
|
||||||
// Get init image
|
// Get init image
|
||||||
auxCtx.fillRect(0, 0, request.width, request.height);
|
auxCtx.fillRect(0, 0, request.width, request.height);
|
||||||
auxCtx.drawImage(
|
auxCtx.drawImage(
|
||||||
uiLayers.active.canvas,
|
visibleCanvas,
|
||||||
bb.x,
|
0,
|
||||||
bb.y,
|
0,
|
||||||
bb.w,
|
bb.w,
|
||||||
bb.h,
|
bb.h,
|
||||||
0,
|
0,
|
||||||
|
@ -637,13 +646,20 @@ const _reticle_draw = (evn, state) => {
|
||||||
state.snapToGrid && basePixelCount
|
state.snapToGrid && basePixelCount
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const cvp = viewport.canvasToView(evn.x, evn.y);
|
||||||
|
const bbvp = {
|
||||||
|
...viewport.canvasToView(bb.x, bb.y),
|
||||||
|
w: viewport.zoom * bb.w,
|
||||||
|
h: viewport.zoom * bb.h,
|
||||||
|
};
|
||||||
|
|
||||||
// draw targeting square reticle thingy cursor
|
// draw targeting square reticle thingy cursor
|
||||||
ovCtx.lineWidth = 1;
|
uiCtx.lineWidth = 1;
|
||||||
ovCtx.strokeStyle = "#FFF";
|
uiCtx.strokeStyle = "#FFF";
|
||||||
ovCtx.strokeRect(bb.x, bb.y, bb.w, bb.h); //origin is middle of the frame
|
uiCtx.strokeRect(bbvp.x, bbvp.y, bbvp.w, bbvp.h); //origin is middle of the frame
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
ovCtx.clearRect(bb.x - 10, bb.y - 10, bb.w + 20, bb.h + 20);
|
uiCtx.clearRect(bbvp.x - 10, bbvp.y - 10, bbvp.w + 20, bbvp.h + 20);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -652,6 +668,7 @@ const _reticle_draw = (evn, state) => {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const _dream_onwheel = (evn, state) => {
|
const _dream_onwheel = (evn, state) => {
|
||||||
|
state.mousemovecb(evn);
|
||||||
if (!evn.evn.ctrlKey) {
|
if (!evn.evn.ctrlKey) {
|
||||||
const v =
|
const v =
|
||||||
state.cursorSize -
|
state.cursorSize -
|
||||||
|
@ -670,7 +687,7 @@ const dreamTool = () =>
|
||||||
"Dream",
|
"Dream",
|
||||||
(state, opt) => {
|
(state, opt) => {
|
||||||
// Draw new cursor immediately
|
// Draw new cursor immediately
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
|
||||||
state.mousemovecb({
|
state.mousemovecb({
|
||||||
...mouse.coords.world.pos,
|
...mouse.coords.world.pos,
|
||||||
});
|
});
|
||||||
|
@ -693,6 +710,8 @@ const dreamTool = () =>
|
||||||
|
|
||||||
// Hide Mask
|
// Hide Mask
|
||||||
setMask("none");
|
setMask("none");
|
||||||
|
|
||||||
|
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
init: (state) => {
|
init: (state) => {
|
||||||
|
@ -707,7 +726,7 @@ const dreamTool = () =>
|
||||||
state.overMaskPx = 0;
|
state.overMaskPx = 0;
|
||||||
|
|
||||||
state.erasePrevReticle = () =>
|
state.erasePrevReticle = () =>
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
|
||||||
|
|
||||||
state.mousemovecb = (evn) => {
|
state.mousemovecb = (evn) => {
|
||||||
state.erasePrevReticle();
|
state.erasePrevReticle();
|
||||||
|
@ -789,7 +808,7 @@ const img2imgTool = () =>
|
||||||
"Img2Img",
|
"Img2Img",
|
||||||
(state, opt) => {
|
(state, opt) => {
|
||||||
// Draw new cursor immediately
|
// Draw new cursor immediately
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
|
||||||
state.mousemovecb({
|
state.mousemovecb({
|
||||||
...mouse.coords.world.pos,
|
...mouse.coords.world.pos,
|
||||||
});
|
});
|
||||||
|
@ -802,6 +821,8 @@ const img2imgTool = () =>
|
||||||
|
|
||||||
// Display Mask
|
// Display Mask
|
||||||
setMask(state.invertMask ? "hold" : "clear");
|
setMask(state.invertMask ? "hold" : "clear");
|
||||||
|
|
||||||
|
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
|
||||||
},
|
},
|
||||||
(state, opt) => {
|
(state, opt) => {
|
||||||
// Clear Listeners
|
// Clear Listeners
|
||||||
|
@ -812,6 +833,7 @@ const img2imgTool = () =>
|
||||||
|
|
||||||
// Hide mask
|
// Hide mask
|
||||||
setMask("none");
|
setMask("none");
|
||||||
|
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
init: (state) => {
|
init: (state) => {
|
||||||
|
@ -829,7 +851,7 @@ const img2imgTool = () =>
|
||||||
state.keepBorderSize = 64;
|
state.keepBorderSize = 64;
|
||||||
|
|
||||||
state.erasePrevReticle = () =>
|
state.erasePrevReticle = () =>
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
|
||||||
|
|
||||||
state.mousemovecb = (evn) => {
|
state.mousemovecb = (evn) => {
|
||||||
state.erasePrevReticle();
|
state.erasePrevReticle();
|
||||||
|
@ -848,6 +870,12 @@ const img2imgTool = () =>
|
||||||
height: stableDiffusionData.height,
|
height: stableDiffusionData.height,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const bbvp = {
|
||||||
|
...viewport.canvasToView(bb.x, bb.y),
|
||||||
|
w: viewport.zoom * bb.w,
|
||||||
|
h: viewport.zoom * bb.h,
|
||||||
|
};
|
||||||
|
|
||||||
// For displaying border mask
|
// For displaying border mask
|
||||||
const auxCanvas = document.createElement("canvas");
|
const auxCanvas = document.createElement("canvas");
|
||||||
auxCanvas.width = request.width;
|
auxCanvas.width = request.width;
|
||||||
|
@ -870,16 +898,16 @@ const img2imgTool = () =>
|
||||||
request.width,
|
request.width,
|
||||||
state.keepBorderSize
|
state.keepBorderSize
|
||||||
);
|
);
|
||||||
ovCtx.drawImage(
|
uiCtx.drawImage(
|
||||||
auxCanvas,
|
auxCanvas,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
request.width,
|
request.width,
|
||||||
request.height,
|
request.height,
|
||||||
bb.x,
|
bbvp.x,
|
||||||
bb.y,
|
bbvp.y,
|
||||||
bb.w,
|
bbvp.w,
|
||||||
bb.h
|
bbvp.h
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -52,50 +52,14 @@ const _mask_brush_erase_callback = (evn, state) => {
|
||||||
maskPaintCtx.stroke();
|
maskPaintCtx.stroke();
|
||||||
};
|
};
|
||||||
|
|
||||||
const _paint_mb_cursor = (state) => {
|
|
||||||
const v = state.brushSize;
|
|
||||||
state.cursorLayer.resize(v + 20, v + 20);
|
|
||||||
|
|
||||||
const ctx = state.cursorLayer.ctx;
|
|
||||||
|
|
||||||
ctx.clearRect(0, 0, v + 20, v + 20);
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(
|
|
||||||
(v + 20) / 2,
|
|
||||||
(v + 20) / 2,
|
|
||||||
state.brushSize / 2,
|
|
||||||
0,
|
|
||||||
2 * Math.PI,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
ctx.fillStyle = "#FFFFFF50";
|
|
||||||
|
|
||||||
ctx.fill();
|
|
||||||
|
|
||||||
if (state.preview) {
|
|
||||||
ctx.strokeStyle = "#000F";
|
|
||||||
ctx.setLineDash([4, 2]);
|
|
||||||
ctx.stroke();
|
|
||||||
ctx.setLineDash([]);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const maskBrushTool = () =>
|
const maskBrushTool = () =>
|
||||||
toolbar.registerTool(
|
toolbar.registerTool(
|
||||||
"res/icons/paintbrush.svg",
|
"res/icons/paintbrush.svg",
|
||||||
"Mask Brush",
|
"Mask Brush",
|
||||||
(state, opt) => {
|
(state, opt) => {
|
||||||
// New layer for the cursor
|
|
||||||
state.cursorLayer = imageCollection.registerLayer(null, {
|
|
||||||
after: maskPaintLayer,
|
|
||||||
bb: {x: 0, y: 0, w: state.brushSize + 20, h: state.brushSize + 20},
|
|
||||||
});
|
|
||||||
|
|
||||||
_paint_mb_cursor(state);
|
|
||||||
|
|
||||||
// Draw new cursor immediately
|
// Draw new cursor immediately
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
|
||||||
state.movecb({...mouse.coords.world.pos});
|
state.redraw();
|
||||||
|
|
||||||
// Start Listeners
|
// Start Listeners
|
||||||
mouse.listen.world.onmousemove.on(state.movecb);
|
mouse.listen.world.onmousemove.on(state.movecb);
|
||||||
|
@ -109,10 +73,6 @@ const maskBrushTool = () =>
|
||||||
setMask("neutral");
|
setMask("neutral");
|
||||||
},
|
},
|
||||||
(state, opt) => {
|
(state, opt) => {
|
||||||
// Don't want to keep hogging resources
|
|
||||||
imageCollection.deleteLayer(state.cursorLayer);
|
|
||||||
state.cursorLayer = null;
|
|
||||||
|
|
||||||
// Clear Listeners
|
// Clear Listeners
|
||||||
mouse.listen.world.onmousemove.clear(state.movecb);
|
mouse.listen.world.onmousemove.clear(state.movecb);
|
||||||
mouse.listen.world.onwheel.clear(state.wheelcb);
|
mouse.listen.world.onwheel.clear(state.wheelcb);
|
||||||
|
@ -126,6 +86,8 @@ const maskBrushTool = () =>
|
||||||
state.ctxmenu.previewMaskButton.classList.remove("active");
|
state.ctxmenu.previewMaskButton.classList.remove("active");
|
||||||
maskPaintCanvas.classList.remove("opaque");
|
maskPaintCanvas.classList.remove("opaque");
|
||||||
state.preview = false;
|
state.preview = false;
|
||||||
|
|
||||||
|
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
init: (state) => {
|
init: (state) => {
|
||||||
|
@ -145,21 +107,41 @@ const maskBrushTool = () =>
|
||||||
state.preview = false;
|
state.preview = false;
|
||||||
|
|
||||||
state.clearPrevCursor = () =>
|
state.clearPrevCursor = () =>
|
||||||
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
|
||||||
|
|
||||||
|
state.redraw = () => {
|
||||||
|
state.movecb({
|
||||||
|
...mouse.coords.world.pos,
|
||||||
|
evn: {
|
||||||
|
clientX: mouse.coords.window.pos.x,
|
||||||
|
clientY: mouse.coords.window.pos.y,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
state.movecb = (evn) => {
|
state.movecb = (evn) => {
|
||||||
state.cursorLayer.moveTo(
|
const vcp = {x: evn.evn.clientX, y: evn.evn.clientY};
|
||||||
evn.x - state.brushSize / 2 - 10,
|
const scp = state.brushSize * viewport.zoom;
|
||||||
evn.y - state.brushSize / 2 - 10
|
|
||||||
);
|
|
||||||
|
|
||||||
state.clearPrevCursor = () =>
|
state.clearPrevCursor();
|
||||||
ovCtx.clearRect(
|
state;
|
||||||
evn.x - state.brushSize / 2 - 10,
|
clearPrevCursor = () =>
|
||||||
evn.y - state.brushSize / 2 - 10,
|
uiCtx.clearRect(
|
||||||
evn.x + state.brushSize / 2 + 10,
|
vcp.x - scp / 2 - 10,
|
||||||
evn.y + state.brushSize / 2 + 10
|
vcp.y - scp / 2 - 10,
|
||||||
|
vcp.x + scp / 2 + 10,
|
||||||
|
vcp.y + scp / 2 + 10
|
||||||
);
|
);
|
||||||
|
|
||||||
|
uiCtx.beginPath();
|
||||||
|
uiCtx.arc(vcp.x, vcp.y, scp / 2, 0, 2 * Math.PI, true);
|
||||||
|
uiCtx.strokeStyle = "black";
|
||||||
|
uiCtx.stroke();
|
||||||
|
|
||||||
|
uiCtx.beginPath();
|
||||||
|
uiCtx.arc(vcp.x, vcp.y, scp / 2, 0, 2 * Math.PI, true);
|
||||||
|
uiCtx.fillStyle = "#FFFFFF50";
|
||||||
|
uiCtx.fill();
|
||||||
};
|
};
|
||||||
|
|
||||||
state.wheelcb = (evn) => {
|
state.wheelcb = (evn) => {
|
||||||
|
@ -169,6 +151,8 @@ const maskBrushTool = () =>
|
||||||
Math.floor(state.config.brushScrollSpeed * evn.delta)
|
Math.floor(state.config.brushScrollSpeed * evn.delta)
|
||||||
);
|
);
|
||||||
state.movecb(evn);
|
state.movecb(evn);
|
||||||
|
} else {
|
||||||
|
state.movecb(evn);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -189,7 +173,8 @@ const maskBrushTool = () =>
|
||||||
textStep: 1,
|
textStep: 1,
|
||||||
cb: (v) => {
|
cb: (v) => {
|
||||||
if (!state.cursorLayer) return;
|
if (!state.cursorLayer) return;
|
||||||
_paint_mb_cursor(state);
|
|
||||||
|
state.redraw();
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -221,11 +206,12 @@ const maskBrushTool = () =>
|
||||||
if (previewMaskButton.classList.contains("active")) {
|
if (previewMaskButton.classList.contains("active")) {
|
||||||
maskPaintCanvas.classList.remove("opaque");
|
maskPaintCanvas.classList.remove("opaque");
|
||||||
state.preview = false;
|
state.preview = false;
|
||||||
_paint_mb_cursor(state);
|
|
||||||
|
state.redraw();
|
||||||
} else {
|
} else {
|
||||||
maskPaintCanvas.classList.add("opaque");
|
maskPaintCanvas.classList.add("opaque");
|
||||||
state.preview = true;
|
state.preview = true;
|
||||||
_paint_mb_cursor(state);
|
state.redraw();
|
||||||
}
|
}
|
||||||
previewMaskButton.classList.toggle("active");
|
previewMaskButton.classList.toggle("active");
|
||||||
};
|
};
|
||||||
|
|
|
@ -84,7 +84,7 @@ const selectTransformTool = () =>
|
||||||
// Clears selection and make things right
|
// Clears selection and make things right
|
||||||
state.reset = () => {
|
state.reset = () => {
|
||||||
if (state.selected)
|
if (state.selected)
|
||||||
uiLayers.active.ctx.drawImage(
|
uil.ctx.drawImage(
|
||||||
state.original.image,
|
state.original.image,
|
||||||
state.original.x,
|
state.original.x,
|
||||||
state.original.y
|
state.original.y
|
||||||
|
@ -312,7 +312,7 @@ const selectTransformTool = () =>
|
||||||
|
|
||||||
// If something is selected, commit changes to the canvas
|
// If something is selected, commit changes to the canvas
|
||||||
if (state.selected) {
|
if (state.selected) {
|
||||||
uiLayers.active.ctx.drawImage(
|
uil.ctx.drawImage(
|
||||||
state.selected.image,
|
state.selected.image,
|
||||||
state.original.x,
|
state.original.x,
|
||||||
state.original.y
|
state.original.y
|
||||||
|
@ -406,7 +406,7 @@ const selectTransformTool = () =>
|
||||||
const ctx = cvs.getContext("2d");
|
const ctx = cvs.getContext("2d");
|
||||||
|
|
||||||
ctx.drawImage(
|
ctx.drawImage(
|
||||||
uiLayers.active.canvas,
|
uil.canvas,
|
||||||
state.selected.x,
|
state.selected.x,
|
||||||
state.selected.y,
|
state.selected.y,
|
||||||
state.selected.w,
|
state.selected.w,
|
||||||
|
@ -417,7 +417,7 @@ const selectTransformTool = () =>
|
||||||
state.selected.h
|
state.selected.h
|
||||||
);
|
);
|
||||||
|
|
||||||
uiLayers.active.ctx.clearRect(
|
uil.ctx.clearRect(
|
||||||
state.selected.x,
|
state.selected.x,
|
||||||
state.selected.y,
|
state.selected.y,
|
||||||
state.selected.w,
|
state.selected.w,
|
||||||
|
|
|
@ -46,6 +46,8 @@ const stampTool = () =>
|
||||||
Array.from(state.ctxmenu.resourceList.children).forEach((child) => {
|
Array.from(state.ctxmenu.resourceList.children).forEach((child) => {
|
||||||
child.classList.remove("selected");
|
child.classList.remove("selected");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ovCtx.clearRect(0, 0, ovCanvas.width, ovCanvas.height);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
init: (state) => {
|
init: (state) => {
|
||||||
|
|
5
res/icons/chevron-down.svg
Normal file
5
res/icons/chevron-down.svg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor"
|
||||||
|
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<polyline points="6 9 12 15 18 9"></polyline>
|
||||||
|
|
||||||
|
</svg>
|
After Width: | Height: | Size: 239 B |
4
res/icons/chevron-up.svg
Normal file
4
res/icons/chevron-up.svg
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<polyline points="18 15 12 9 6 15"></polyline>
|
||||||
|
|
||||||
|
</svg>
|
After Width: | Height: | Size: 238 B |
7
res/icons/eye-off.svg
Normal file
7
res/icons/eye-off.svg
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M9.88 9.88a3 3 0 1 0 4.24 4.24"></path>
|
||||||
|
<path d="M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68"></path>
|
||||||
|
<path d="M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61"></path>
|
||||||
|
<line x1="2" y1="2" x2="22" y2="22"></line>
|
||||||
|
|
||||||
|
</svg>
|
After Width: | Height: | Size: 474 B |
5
res/icons/eye.svg
Normal file
5
res/icons/eye.svg
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"></path>
|
||||||
|
<circle cx="12" cy="12" r="3"></circle>
|
||||||
|
|
||||||
|
</svg>
|
After Width: | Height: | Size: 296 B |
7
res/icons/file-plus.svg
Normal file
7
res/icons/file-plus.svg
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z"></path>
|
||||||
|
<polyline points="14 2 14 8 20 8"></polyline>
|
||||||
|
<line x1="12" y1="18" x2="12" y2="12"></line>
|
||||||
|
<line x1="9" y1="15" x2="15" y2="15"></line>
|
||||||
|
|
||||||
|
</svg>
|
After Width: | Height: | Size: 422 B |
Loading…
Reference in a new issue