Merge branch 'main' into clear_brush_mask_option

This commit is contained in:
tim h 2022-12-21 19:36:06 -06:00
commit 01dbafb17f
14 changed files with 381 additions and 97 deletions

View file

@ -318,6 +318,7 @@
type="text/javascript"></script> type="text/javascript"></script>
<!-- Content --> <!-- Content -->
<script src="js/global.js" type="text/javascript"></script>
<script src="js/prompt.js" type="text/javascript"></script> <script src="js/prompt.js" type="text/javascript"></script>
<script src="js/index.js" type="text/javascript"></script> <script src="js/index.js" type="text/javascript"></script>

22
js/global.js Normal file
View file

@ -0,0 +1,22 @@
/**
* Stores global variables without polluting the global namespace.
*/
const global = {
// Connection
_connection: "offline",
set connection(v) {
this._connection = v;
toolbar &&
toolbar.currentTool &&
toolbar.currentTool.state.redraw &&
toolbar.currentTool.state.redraw();
},
get connection() {
return this._connection;
},
// If there is a selected input
hasActiveInput: false,
};

View file

@ -188,6 +188,7 @@ function startup() {
} }
function setFixedHost(h, changePromptMessage) { function setFixedHost(h, changePromptMessage) {
console.info(`[index] Fixed host to '${h}'`);
const hostInput = document.getElementById("host"); const hostInput = document.getElementById("host");
host = h; host = h;
hostInput.value = h; hostInput.value = h;
@ -340,7 +341,11 @@ async function testHostConnection() {
}, },
}; };
statuses[status] && statuses[status](); statuses[status] &&
(() => {
statuses[status]();
global.connection = status;
})();
}; };
setConnectionStatus("before"); setConnectionStatus("before");
@ -411,7 +416,7 @@ async function testHostConnection() {
return status; return status;
}; };
await checkConnection(true); await checkConnection(!urlParams.has("noprompt"));
// On click, attempt to refresh // On click, attempt to refresh
connectionIndicator.onclick = async () => { connectionIndicator.onclick = async () => {
@ -457,6 +462,8 @@ function clearPaintedMask() {
function march(bb, options = {}) { function march(bb, options = {}) {
defaultOpt(options, { defaultOpt(options, {
title: null,
titleStyle: "#FFF5",
style: "#FFFF", style: "#FFFF",
width: "2px", width: "2px",
filter: null, filter: null,
@ -471,6 +478,7 @@ function march(bb, options = {}) {
// Get temporary layer to draw marching ants // Get temporary layer to draw marching ants
const layer = imageCollection.registerLayer(null, { const layer = imageCollection.registerLayer(null, {
bb: expanded, bb: expanded,
category: "display",
}); });
layer.canvas.style.imageRendering = "pixelated"; layer.canvas.style.imageRendering = "pixelated";
let offset = 0; let offset = 0;
@ -490,6 +498,16 @@ function drawMarchingAnts(ctx, bb, offset, options) {
ctx.save(); ctx.save();
ctx.clearRect(0, 0, bb.w + 2, bb.h + 2); ctx.clearRect(0, 0, bb.w + 2, bb.h + 2);
// Draw Tool Name
if (bb.h > 40 && options.title) {
ctx.font = `bold 20px Open Sans`;
ctx.textAlign = "left";
ctx.fillStyle = options.titleStyle;
ctx.fillText(options.title, 10, 30, bb.w);
}
ctx.strokeStyle = options.style; ctx.strokeStyle = options.style;
ctx.strokeWidth = options.width; ctx.strokeWidth = options.width;
ctx.filter = options.filter; ctx.filter = options.filter;
@ -920,6 +938,12 @@ async function getSamplers() {
try { try {
const response = await fetch(url); const response = await fetch(url);
const data = await response.json(); const data = await response.json();
samplerAutoComplete.onchange.on(({value}) => {
stableDiffusionData.sampler_index = value;
localStorage.setItem("openoutpaint/sampler", value);
});
samplerAutoComplete.options = data.map((sampler) => ({ samplerAutoComplete.options = data.map((sampler) => ({
name: sampler.name, name: sampler.name,
value: sampler.name, value: sampler.name,
@ -932,11 +956,7 @@ async function getSamplers() {
samplerAutoComplete.value = data[0].name; samplerAutoComplete.value = data[0].name;
localStorage.setItem("openoutpaint/sampler", samplerAutoComplete.value); localStorage.setItem("openoutpaint/sampler", samplerAutoComplete.value);
} }
stableDiffusionData.sampler_index = samplerAutoComplete.value;
samplerAutoComplete.onchange.on(({value}) => {
stableDiffusionData.sampler_index = value;
localStorage.setItem("openoutpaint/sampler", value);
});
} catch (e) { } catch (e) {
console.warn("[index] Failed to fetch samplers"); console.warn("[index] Failed to fetch samplers");
console.warn(e); console.warn(e);

View file

@ -20,20 +20,25 @@ const imageCollection = layers.registerCollection(
const bgLayer = imageCollection.registerLayer("bg", { const bgLayer = imageCollection.registerLayer("bg", {
name: "Background", name: "Background",
category: "background",
}); });
const imgLayer = imageCollection.registerLayer("image", { const imgLayer = imageCollection.registerLayer("image", {
name: "Image", name: "Image",
category: "image",
ctxOptions: {desynchronized: true}, ctxOptions: {desynchronized: true},
}); });
const maskPaintLayer = imageCollection.registerLayer("mask", { const maskPaintLayer = imageCollection.registerLayer("mask", {
name: "Mask Paint", name: "Mask Paint",
category: "mask",
ctxOptions: {desynchronized: true}, ctxOptions: {desynchronized: true},
}); });
const ovLayer = imageCollection.registerLayer("overlay", { const ovLayer = imageCollection.registerLayer("overlay", {
name: "Overlay", name: "Overlay",
category: "display",
}); });
const debugLayer = imageCollection.registerLayer("debug", { const debugLayer = imageCollection.registerLayer("debug", {
name: "Debug Layer", name: "Debug Layer",
category: "display",
}); });
const imgCanvas = imgLayer.canvas; // where dreams go const imgCanvas = imgLayer.canvas; // where dreams go
@ -237,9 +242,28 @@ mouse.registerContext(
ctx.coords.pos.x = Math.round(layerCoords.x); ctx.coords.pos.x = Math.round(layerCoords.x);
ctx.coords.pos.y = Math.round(layerCoords.y); ctx.coords.pos.y = Math.round(layerCoords.y);
}, },
{target: imageCollection.inputElement} {
target: imageCollection.inputElement,
validate: (evn) => {
if (!global.hasActiveInput || evn.type === "mousemove") return true;
return false;
},
}
); );
// Redraw on active input state change
(() => {
mouse.listen.window.onany.on((evn) => {
const activeInput = DOM.hasActiveInput();
if (global.hasActiveInput !== activeInput) {
global.hasActiveInput = activeInput;
toolbar.currentTool &&
toolbar.currentTool.state.redraw &&
toolbar.currentTool.state.redraw();
}
});
})();
mouse.listen.window.onwheel.on((evn) => { mouse.listen.window.onwheel.on((evn) => {
if (evn.evn.ctrlKey) { if (evn.evn.ctrlKey) {
evn.evn.preventDefault(); evn.evn.preventDefault();

View file

@ -42,6 +42,7 @@
* An object for mouse event listeners * An object for mouse event listeners
* *
* @typedef MouseListenerContext * @typedef MouseListenerContext
* @property {Observer} onany A listener for any mouse events
* @property {Observer} onmousemove A mouse move handler * @property {Observer} onmousemove A mouse move handler
* @property {Observer} onwheel A mouse wheel handler * @property {Observer} onwheel A mouse wheel handler
* @property {Record<string, MouseListenerBtnContext>} btn Button handlers * @property {Record<string, MouseListenerBtnContext>} btn Button handlers
@ -67,6 +68,7 @@
* @property {ContextMoveTransformer} onmove The coordinate transform callback * @property {ContextMoveTransformer} onmove The coordinate transform callback
* @property {(evn) => void} onany A function to be run on any event * @property {(evn) => void} onany A function to be run on any event
* @property {?HTMLElement} target The target * @property {?HTMLElement} target The target
* @property {(evn) => boolean} validate A function to be check if we will process an event
* @property {MouseCoordContext} coords Coordinates object * @property {MouseCoordContext} coords Coordinates object
* @property {MouseListenerContext} listen Listeners object * @property {MouseListenerContext} listen Listeners object
*/ */

View file

@ -63,16 +63,16 @@ const mouse = {
* @param {ContextMoveTransformer} onmove The function to perform coordinate transform * @param {ContextMoveTransformer} onmove The function to perform coordinate transform
* @param {object} options Extra options * @param {object} options Extra options
* @param {HTMLElement} [options.target=null] Target filtering * @param {HTMLElement} [options.target=null] Target filtering
* @param {(evn: any) => boolean} [options.validate] Checks if we will process this event or not
* @param {Record<number, string>} [options.buttons={0: "left", 1: "middle", 2: "right"}] Custom button mapping * @param {Record<number, string>} [options.buttons={0: "left", 1: "middle", 2: "right"}] Custom button mapping
* @param {(evn) => void} [options.genericcb=null] Function that will be run for all events (useful for preventDefault)
* @returns {MouseContext} * @returns {MouseContext}
*/ */
registerContext: (name, onmove, options = {}) => { registerContext: (name, onmove, options = {}) => {
// Options // Options
defaultOpt(options, { defaultOpt(options, {
target: null, target: null,
validate: () => true,
buttons: {0: "left", 1: "middle", 2: "right"}, buttons: {0: "left", 1: "middle", 2: "right"},
genericcb: null,
}); });
// Context information // Context information
@ -81,8 +81,8 @@ const mouse = {
id: guid(), id: guid(),
name, name,
onmove, onmove,
onany: options.genericcb,
target: options.target, target: options.target,
validate: options.validate,
buttons: options.buttons, buttons: options.buttons,
}; };
@ -102,12 +102,27 @@ const mouse = {
}; };
// Listeners // Listeners
const onany = new Observer();
mouse.listen[name] = { mouse.listen[name] = {
onany,
onwheel: new Observer(), onwheel: new Observer(),
onmousemove: new Observer(), onmousemove: new Observer(),
btn: {}, btn: {},
}; };
// Always process onany events first
mouse.listen[name].onwheel.on(
async (evn, state) => await onany.emit(evn, state),
Infinity,
true
);
mouse.listen[name].onmousemove.on(
async (evn, state) => await onany.emit(evn, state),
Infinity,
true
);
// Button specific items // Button specific items
Object.keys(options.buttons).forEach((index) => { Object.keys(options.buttons).forEach((index) => {
const button = options.buttons[index]; const button = options.buttons[index];
@ -115,6 +130,48 @@ const mouse = {
mouse.listen[name].btn[button] = _mouse_observers( mouse.listen[name].btn[button] = _mouse_observers(
`mouse.listen[${name}].btn[${button}]` `mouse.listen[${name}].btn[${button}]`
); );
// Always process onany events first
mouse.listen[name].btn[button].onclick.on(
async (evn, state) => await onany.emit(evn, state),
Infinity,
true
);
mouse.listen[name].btn[button].ondclick.on(
async (evn, state) => await onany.emit(evn, state),
Infinity,
true
);
mouse.listen[name].btn[button].ondragstart.on(
async (evn, state) => await onany.emit(evn, state),
Infinity,
true
);
mouse.listen[name].btn[button].ondrag.on(
async (evn, state) => await onany.emit(evn, state),
Infinity,
true
);
mouse.listen[name].btn[button].ondragend.on(
async (evn, state) => await onany.emit(evn, state),
Infinity,
true
);
mouse.listen[name].btn[button].onpaintstart.on(
async (evn, state) => await onany.emit(evn, state),
Infinity,
true
);
mouse.listen[name].btn[button].onpaint.on(
async (evn, state) => await onany.emit(evn, state),
Infinity,
true
);
mouse.listen[name].btn[button].onpaintend.on(
async (evn, state) => await onany.emit(evn, state),
Infinity,
true
);
}); });
// Add to context // Add to context
@ -183,11 +240,13 @@ window.addEventListener(
mouse.buttons[evn.button] = time; mouse.buttons[evn.button] = time;
mouse._contexts.forEach(({target, name, buttons, onany}) => { mouse._contexts.forEach(({target, name, buttons, validate}) => {
const key = buttons[evn.button]; const key = buttons[evn.button];
if ((!target || target === evn.target) && key) { if (
onany && onany(); (!target || target === evn.target) &&
key &&
(!validate || validate(evn))
) {
mouse.coords[name].dragging[key] = {}; mouse.coords[name].dragging[key] = {};
mouse.coords[name].dragging[key].target = evn.target; mouse.coords[name].dragging[key].target = evn.target;
Object.assign(mouse.coords[name].dragging[key], mouse.coords[name].pos); Object.assign(mouse.coords[name].dragging[key], mouse.coords[name].pos);
@ -214,14 +273,14 @@ window.addEventListener(
(evn) => { (evn) => {
const time = performance.now(); const time = performance.now();
mouse._contexts.forEach(({target, name, buttons, onany}) => { mouse._contexts.forEach(({target, name, buttons, validate}) => {
const key = buttons[evn.button]; const key = buttons[evn.button];
if ( if (
(!target || target === evn.target) && (!target || target === evn.target) &&
key && key &&
mouse.coords[name].dragging[key] mouse.coords[name].dragging[key] &&
(!validate || validate(evn))
) { ) {
onany && onany();
const start = { const start = {
x: mouse.coords[name].dragging[key].x, x: mouse.coords[name].dragging[key].x,
y: mouse.coords[name].dragging[key].y, y: mouse.coords[name].dragging[key].y,
@ -292,7 +351,10 @@ window.addEventListener(
const target = context.target; const target = context.target;
const name = context.name; const name = context.name;
if (!target || target === evn.target) { if (
!target ||
(target === evn.target && (!context.validate || context.validate(evn)))
) {
context.onmove(evn, context); context.onmove(evn, context);
mouse.listen[name].onmousemove.emit({ mouse.listen[name].onmousemove.emit({
@ -378,7 +440,8 @@ window.addEventListener(
window.addEventListener( window.addEventListener(
"wheel", "wheel",
(evn) => { (evn) => {
mouse._contexts.forEach(({name}) => { mouse._contexts.forEach(({name, target, validate}) => {
if (!target || (target === evn.target && (!validate || validate(evn)))) {
mouse.listen[name].onwheel.emit({ mouse.listen[name].onwheel.emit({
target: evn.target, target: evn.target,
delta: evn.deltaY, delta: evn.deltaY,
@ -391,6 +454,7 @@ window.addEventListener(
evn, evn,
timestamp: performance.now(), timestamp: performance.now(),
}); });
}
}); });
}, },
{passive: false} {passive: false}

View file

@ -224,9 +224,6 @@ const layers = {
// Input element (overlay element for input handling) // Input element (overlay element for input handling)
const inputel = document.createElement("div"); const inputel = document.createElement("div");
inputel.id = `collection-input-${id}`; inputel.id = `collection-input-${id}`;
inputel.addEventListener("mouseover", (evn) => {
document.activeElement.blur();
});
inputel.classList.add("collection-input-overlay"); inputel.classList.add("collection-input-overlay");
element.appendChild(inputel); element.appendChild(inputel);
@ -340,6 +337,7 @@ const layers = {
* @param {object} options * @param {object} options
* @param {string} options.name * @param {string} options.name
* @param {?BoundingBox} options.bb * @param {?BoundingBox} options.bb
* @param {string} [options.category]
* @param {{w: number, h: number}} options.resolution * @param {{w: number, h: number}} options.resolution
* @param {?string} options.group * @param {?string} options.group
* @param {object} options.after * @param {object} options.after
@ -362,6 +360,9 @@ const layers = {
h: collection.size.h, h: collection.size.h,
}, },
// Category of the layer
category: null,
// Resolution for layer // Resolution for layer
resolution: null, resolution: null,
@ -451,6 +452,7 @@ const layers = {
key, key,
name: options.name, name: options.name,
full, full,
category: options.category,
state: new Proxy( state: new Proxy(
{visible: true}, {visible: true},
@ -494,6 +496,10 @@ const layers = {
return this._collection.origin; return this._collection.origin;
}, },
get hidden() {
return !this.state.visible;
},
/** Our canvas */ /** Our canvas */
canvas, canvas,
ctx, ctx,

View file

@ -106,9 +106,9 @@ class Observer {
* Sends a message to all observers * Sends a message to all observers
* *
* @param {T} msg The message to send to the observers * @param {T} msg The message to send to the observers
* @param {any} state The initial state
*/ */
async emit(msg) { async emit(msg, state = {}) {
const state = {};
const promises = []; const promises = [];
for (const {handler, wait} of this._handlers) { for (const {handler, wait} of this._handlers) {
const run = async () => { const run = async () => {
@ -128,6 +128,25 @@ class Observer {
} }
} }
/**
* Static DOM utility functions
*/
class DOM {
static inputTags = new Set(["input", "textarea"]);
/**
* Checks if there is an active input
*
* @returns Whether there is currently an active input
*/
static hasActiveInput() {
return (
document.activeElement &&
this.inputTags.has(document.activeElement.tagName.toLowerCase())
);
}
}
/** /**
* Generates a simple UID in the format xxxx-xxxx-...-xxxx, with x being [0-9a-f] * Generates a simple UID in the format xxxx-xxxx-...-xxxx, with x being [0-9a-f]
* *

View file

@ -3,10 +3,17 @@
*/ */
const uil = { const uil = {
/** @type {Observer<{uilayer: UILayer}>} */
onactive: new Observer(),
_ui_layer_list: document.getElementById("layer-list"), _ui_layer_list: document.getElementById("layer-list"),
layers: [], layers: [],
_active: null, _active: null,
set active(v) { set active(v) {
this.onactive.emit({
uilayer: v,
});
Array.from(this._ui_layer_list.children).forEach((child) => { Array.from(this._ui_layer_list.children).forEach((child) => {
child.classList.remove("active"); child.classList.remove("active");
}); });
@ -188,6 +195,7 @@ const uil = {
_addLayer(group, name) { _addLayer(group, name) {
const layer = imageCollection.registerLayer(null, { const layer = imageCollection.registerLayer(null, {
name, name,
category: "user",
after: after:
(this.layers.length > 0 && this.layers[this.layers.length - 1].layer) || (this.layers.length > 0 && this.layers[this.layers.length - 1].layer) ||
bgLayer, bgLayer,
@ -285,11 +293,13 @@ const uil = {
* @param {BoundingBox} bb The bouding box to get visible data from * @param {BoundingBox} bb The bouding box to get visible data from
* @param {object} [options] Options * @param {object} [options] Options
* @param {boolean} [options.includeBg=false] Whether to include the background * @param {boolean} [options.includeBg=false] Whether to include the background
* @param {string[]} [options.categories] Categories of layers to consider visible
* @returns {HTMLCanvasElement} The canvas element containing visible image data * @returns {HTMLCanvasElement} The canvas element containing visible image data
*/ */
getVisible(bb, options = {}) { getVisible(bb, options = {}) {
defaultOpt(options, { defaultOpt(options, {
includeBg: false, includeBg: false,
categories: ["user", "image"],
}); });
const canvas = document.createElement("canvas"); const canvas = document.createElement("canvas");
@ -297,21 +307,14 @@ const uil = {
canvas.width = bb.w; canvas.width = bb.w;
canvas.height = bb.h; 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); const categories = new Set(options.categories);
this.layers.forEach((layer) => { if (options.includeBg) categories.add("background");
if (!layer.hidden) const layers = imageCollection._layers;
ctx.drawImage(
layer.layer.canvas, layers.reduceRight((_, layer) => {
bb.x, if (categories.has(layer.category) && !layer.hidden)
bb.y, ctx.drawImage(layer.canvas, bb.x, bb.y, bb.w, bb.h, 0, 0, bb.w, bb.h);
bb.w,
bb.h,
0,
0,
bb.w,
bb.h
);
}); });
return canvas; return canvas;
@ -336,6 +339,7 @@ commands.createCommand(
const layer = imageCollection.registerLayer(null, { const layer = imageCollection.registerLayer(null, {
name, name,
category: "user",
after: after:
(uil.layers.length > 0 && uil.layers[uil.layers.length - 1].layer) || (uil.layers.length > 0 && uil.layers[uil.layers.length - 1].layer) ||
bgLayer, bgLayer,

View file

@ -62,16 +62,19 @@ const colorBrushTool = () =>
state.drawLayer = imageCollection.registerLayer(null, { state.drawLayer = imageCollection.registerLayer(null, {
after: imgLayer, after: imgLayer,
category: "display",
ctxOptions: {willReadFrequently: true}, ctxOptions: {willReadFrequently: true},
}); });
state.drawLayer.canvas.style.filter = "opacity(70%)"; state.drawLayer.canvas.style.filter = "opacity(70%)";
state.eraseLayer = imageCollection.registerLayer(null, { state.eraseLayer = imageCollection.registerLayer(null, {
after: imgLayer, after: imgLayer,
category: "processing",
ctxOptions: {willReadFrequently: true}, ctxOptions: {willReadFrequently: true},
}); });
state.eraseLayer.hide(); state.eraseLayer.hide();
state.eraseBackup = imageCollection.registerLayer(null, { state.eraseBackup = imageCollection.registerLayer(null, {
after: imgLayer, after: imgLayer,
category: "processing",
}); });
state.eraseBackup.hide(); state.eraseBackup.hide();

View file

@ -24,6 +24,7 @@ const _monitorProgress = (bb, oncheck = null) => {
// Get temporary layer to draw progress bar // Get temporary layer to draw progress bar
const layer = imageCollection.registerLayer(null, { const layer = imageCollection.registerLayer(null, {
bb: expanded, bb: expanded,
category: "display",
}); });
layer.canvas.style.opacity = "70%"; layer.canvas.style.opacity = "70%";
@ -293,6 +294,7 @@ const _generate = async (endpoint, request, bb, options = {}) => {
// Layer for the images // Layer for the images
const layer = imageCollection.registerLayer(null, { const layer = imageCollection.registerLayer(null, {
after: maskPaintLayer, after: maskPaintLayer,
category: "display",
}); });
const redraw = (url = images[at]) => { const redraw = (url = images[at]) => {
@ -1227,7 +1229,7 @@ const dreamTool = () =>
y += snap(evn.y, 0, 64); y += snap(evn.y, 0, 64);
} }
state.erasePrevReticle = _tool._cursor_draw(x, y); state.erasePrevCursor = _tool._cursor_draw(x, y);
if (state.selection.exists) { if (state.selection.exists) {
const bb = state.selection.bb; const bb = state.selection.bb;
@ -1251,6 +1253,8 @@ const dreamTool = () =>
), ),
}, },
{ {
toolTextStyle:
global.connection === "online" ? "#FFF5" : "#F555",
reticleStyle: state.selection.inside ? "#F55" : "#FFF", reticleStyle: state.selection.inside ? "#F55" : "#FFF",
sizeTextStyle: style, sizeTextStyle: style,
} }
@ -1278,6 +1282,7 @@ const dreamTool = () =>
h: stableDiffusionData.height, h: stableDiffusionData.height,
}, },
{ {
toolTextStyle: global.connection === "online" ? "#FFF5" : "#F555",
sizeTextStyle: style, sizeTextStyle: style,
} }
); );
@ -1306,8 +1311,19 @@ const dreamTool = () =>
w: stableDiffusionData.width, w: stableDiffusionData.width,
h: stableDiffusionData.height, h: stableDiffusionData.height,
}; };
if (global.connection === "online") {
dream_generate_callback(bb, resolution, state); dream_generate_callback(bb, resolution, state);
} else {
const stop = march(bb, {
title: "offline",
titleStyle: "#F555",
style: "#F55",
});
setTimeout(stop, 2000);
}
state.selection.deselect(); state.selection.deselect();
state.redraw();
}; };
state.erasecb = (evn, estate) => { state.erasecb = (evn, estate) => {
if (state.selection.exists) { if (state.selection.exists) {
@ -1595,7 +1611,7 @@ const img2imgTool = () =>
y += snap(evn.y, 0, 64); y += snap(evn.y, 0, 64);
} }
state.erasePrevReticle = _tool._cursor_draw(x, y); state.erasePrevCursor = _tool._cursor_draw(x, y);
// Resolution // Resolution
let bb = null; let bb = null;
@ -1624,6 +1640,8 @@ const img2imgTool = () =>
), ),
}, },
{ {
toolTextStyle:
global.connection === "online" ? "#FFF5" : "#F555",
reticleStyle: state.selection.inside ? "#F55" : "#FFF", reticleStyle: state.selection.inside ? "#F55" : "#FFF",
sizeTextStyle: style, sizeTextStyle: style,
} }
@ -1653,6 +1671,8 @@ const img2imgTool = () =>
"Img2Img", "Img2Img",
{w: request.width, h: request.height}, {w: request.width, h: request.height},
{ {
toolTextStyle:
global.connection === "online" ? "#FFF5" : "#F555",
sizeTextStyle: style, sizeTextStyle: style,
} }
); );
@ -1777,7 +1797,16 @@ const img2imgTool = () =>
w: stableDiffusionData.width, w: stableDiffusionData.width,
h: stableDiffusionData.height, h: stableDiffusionData.height,
}; };
if (global.connection === "online") {
dream_img2img_callback(bb, resolution, state); dream_img2img_callback(bb, resolution, state);
} else {
const stop = march(bb, {
title: "offline",
titleStyle: "#F555",
style: "#F55",
});
setTimeout(stop, 2000);
}
state.selection.deselect(); state.selection.deselect();
state.redraw(); state.redraw();
}; };

View file

@ -14,7 +14,7 @@ const _tool = {
* @param {string} [style.genSizeTextStyle = "#FFF5"] Style of the text for diplaying generation size * @param {string} [style.genSizeTextStyle = "#FFF5"] Style of the text for diplaying generation size
* @param {string} [style.toolTextStyle = "#FFF5"] Style of the text for the tool name * @param {string} [style.toolTextStyle = "#FFF5"] Style of the text for the tool name
* @param {number} [style.reticleWidth = 1] Width of the line of the reticle * @param {number} [style.reticleWidth = 1] Width of the line of the reticle
* @param {string} [style.reticleStyle = "#FFF"] Style of the line of the reticle * @param {string} [style.reticleStyle] Style of the line of the reticle
* *
* @returns A function that erases this reticle drawing * @returns A function that erases this reticle drawing
*/ */
@ -24,7 +24,7 @@ const _tool = {
genSizeTextStyle: "#FFF5", genSizeTextStyle: "#FFF5",
toolTextStyle: "#FFF5", toolTextStyle: "#FFF5",
reticleWidth: 1, reticleWidth: 1,
reticleStyle: "#FFF", reticleStyle: global.hasActiveInput ? "#BBF" : "#FFF",
}); });
const bbvp = { const bbvp = {
@ -110,14 +110,14 @@ const _tool = {
* @param {number} y Y world coordinate of the cursor * @param {number} y Y world coordinate of the cursor
* @param {object} style Style of the lines of the cursor * @param {object} style Style of the lines of the cursor
* @param {string} [style.width = 3] Line width of the lines of the cursor * @param {string} [style.width = 3] Line width of the lines of the cursor
* @param {string} [style.style = "#FFF5"] Stroke style of the lines of the cursor * @param {string} [style.style] Stroke style of the lines of the cursor
* *
* @returns A function that erases this cursor drawing * @returns A function that erases this cursor drawing
*/ */
_cursor_draw(x, y, style = {}) { _cursor_draw(x, y, style = {}) {
defaultOpt(style, { defaultOpt(style, {
width: 3, width: 3,
style: "#FFF5", style: global.hasActiveInput ? "#BBF5" : "#FFF5",
}); });
const vpc = viewport.canvasToView(x, y); const vpc = viewport.canvasToView(x, y);

View file

@ -20,6 +20,9 @@ const selectTransformTool = () =>
keyboard.listen.onkeyclick.on(state.keyclickcb); keyboard.listen.onkeyclick.on(state.keyclickcb);
keyboard.listen.onkeydown.on(state.keydowncb); keyboard.listen.onkeydown.on(state.keydowncb);
// Layer system handlers
uil.onactive.on(state.uilayeractivecb);
// Registers keyboard shortcuts // Registers keyboard shortcuts
keyboard.onShortcut({ctrl: true, key: "KeyC"}, state.ctrlccb); keyboard.onShortcut({ctrl: true, key: "KeyC"}, state.ctrlccb);
keyboard.onShortcut({ctrl: true, key: "KeyV"}, state.ctrlvcb); keyboard.onShortcut({ctrl: true, key: "KeyV"}, state.ctrlvcb);
@ -42,6 +45,8 @@ const selectTransformTool = () =>
keyboard.deleteShortcut(state.ctrlvcb, "KeyV"); keyboard.deleteShortcut(state.ctrlvcb, "KeyV");
keyboard.deleteShortcut(state.ctrlxcb, "KeyX"); keyboard.deleteShortcut(state.ctrlxcb, "KeyX");
uil.onactive.clear(state.uilayeractivecb);
// Clear any selections // Clear any selections
state.reset(); state.reset();
@ -61,6 +66,7 @@ const selectTransformTool = () =>
state.useClipboard = !!( state.useClipboard = !!(
navigator.clipboard && navigator.clipboard.write navigator.clipboard && navigator.clipboard.write
); // Use it by default if supported ); // Use it by default if supported
state.selectionPeekOpacity = 40;
state.original = null; state.original = null;
state.dragging = null; state.dragging = null;
@ -78,22 +84,33 @@ const selectTransformTool = () =>
// Some things to easy request for a redraw // Some things to easy request for a redraw
state.lastMouseTarget = null; state.lastMouseTarget = null;
state.lastMouseMove = null; state.lastMouseMove = {x: 0, y: 0};
state.redraw = () => { state.redraw = () => {
ovLayer.clear(); ovLayer.clear();
state.movecb(state.lastMouseMove); state.movecb(state.lastMouseMove);
}; };
state.uilayeractivecb = ({uilayer}) => {
if (state.originalDisplayLayer) {
state.originalDisplayLayer.moveAfter(uilayer.layer);
}
};
// Clears selection and make things right // Clears selection and make things right
state.reset = () => { state.reset = (erase = false) => {
if (state.selected) if (state.selected && !erase)
uil.ctx.drawImage( state.originalLayer.ctx.drawImage(
state.original.image, state.original.image,
state.original.x, state.original.x,
state.original.y state.original.y
); );
if (state.originalDisplayLayer) {
imageCollection.deleteLayer(state.originalDisplayLayer);
state.originalDisplayLayer = null;
}
if (state.dragging) state.dragging = null; if (state.dragging) state.dragging = null;
else state.selected = null; else state.selected = null;
@ -189,6 +206,7 @@ const selectTransformTool = () =>
state.movecb = (evn) => { state.movecb = (evn) => {
ovLayer.clear(); ovLayer.clear();
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height); uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
state.erasePrevCursor && state.erasePrevCursor();
imageCollection.inputElement.style.cursor = "auto"; imageCollection.inputElement.style.cursor = "auto";
state.lastMouseTarget = evn.target; state.lastMouseTarget = evn.target;
state.lastMouseMove = evn; state.lastMouseMove = evn;
@ -254,6 +272,8 @@ const selectTransformTool = () =>
}; };
// Draw Image // Draw Image
ovCtx.save();
ovCtx.filter = `opacity(${state.selectionPeekOpacity}%)`;
ovCtx.drawImage( ovCtx.drawImage(
state.selected.image, state.selected.image,
0, 0,
@ -265,6 +285,22 @@ const selectTransformTool = () =>
state.selected.w, state.selected.w,
state.selected.h state.selected.h
); );
ovCtx.restore();
state.originalDisplayLayer.clear();
state.originalDisplayLayer.ctx.save();
state.originalDisplayLayer.ctx.drawImage(
state.selected.image,
0,
0,
state.selected.image.width,
state.selected.image.height,
state.selected.x,
state.selected.y,
state.selected.w,
state.selected.h
);
state.originalDisplayLayer.ctx.restore();
// Draw selection box // Draw selection box
uiCtx.strokeStyle = "#FFF"; uiCtx.strokeStyle = "#FFF";
@ -320,15 +356,7 @@ const selectTransformTool = () =>
} }
// Draw current cursor location // Draw current cursor location
uiCtx.lineWidth = 3; state.erasePrevCursor = _tool._cursor_draw(x, y);
uiCtx.strokeStyle = "#FFF";
uiCtx.beginPath();
uiCtx.moveTo(vpc.x, vpc.y + 10);
uiCtx.lineTo(vpc.x, vpc.y - 10);
uiCtx.moveTo(vpc.x + 10, vpc.y);
uiCtx.lineTo(vpc.x - 10, vpc.y);
uiCtx.stroke();
uiCtx.restore(); uiCtx.restore();
}; };
@ -337,7 +365,8 @@ const selectTransformTool = () =>
state.clickcb = (evn) => { state.clickcb = (evn) => {
if ( if (
!state.original || !state.original ||
(state.original.x === state.selected.x && (state.originalLayer === uil.layer &&
state.original.x === state.selected.x &&
state.original.y === state.selected.y && state.original.y === state.selected.y &&
state.original.w === state.selected.w && state.original.w === state.selected.w &&
state.original.h === state.selected.h) state.original.h === state.selected.h)
@ -348,16 +377,15 @@ 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) {
uil.ctx.drawImage( state.originalLayer.ctx.drawImage(
state.selected.image, state.selected.image,
state.original.x, state.original.x,
state.original.y state.original.y
); );
commands.runCommand( commands.runCommand("eraseImage", "Image Transform Erase", {
"eraseImage", ...state.original,
"Image Transform Erase", ctx: state.originalLayer.ctx,
state.original });
);
commands.runCommand("drawImage", "Image Transform Draw", { commands.runCommand("drawImage", "Image Transform Draw", {
image: state.selected.image, image: state.selected.image,
x: Math.round(state.selected.x), x: Math.round(state.selected.x),
@ -365,10 +393,7 @@ const selectTransformTool = () =>
w: Math.round(state.selected.w), w: Math.round(state.selected.w),
h: Math.round(state.selected.h), h: Math.round(state.selected.h),
}); });
state.original = null; state.reset(true);
state.selected = null;
state.redraw();
} }
}; };
@ -451,6 +476,11 @@ const selectTransformTool = () =>
x, x,
y y
); );
state.originalLayer = uil.layer;
state.originalDisplayLayer = imageCollection.registerLayer(null, {
after: uil.layer,
category: "select-display",
});
// Cut out selected portion of the image for manipulation // Cut out selected portion of the image for manipulation
const cvs = document.createElement("canvas"); const cvs = document.createElement("canvas");
@ -622,6 +652,22 @@ const selectTransformTool = () =>
if (!(navigator.clipboard && navigator.clipboard.write)) if (!(navigator.clipboard && navigator.clipboard.write))
clipboardCheckbox.checkbox.disabled = true; // Disable if not available clipboardCheckbox.checkbox.disabled = true; // Disable if not available
// Selection Peek Opacity
state.ctxmenu.selectionPeekOpacitySlider = _toolbar_input.slider(
state,
"selectionPeekOpacity",
"Peek Opacity",
{
min: 0,
max: 100,
step: 10,
textStep: 1,
cb: () => {
state.redraw();
},
}
).slider;
// Some useful actions to do with selection // Some useful actions to do with selection
const actionArray = document.createElement("div"); const actionArray = document.createElement("div");
actionArray.classList.add("button-array"); actionArray.classList.add("button-array");
@ -629,7 +675,7 @@ const selectTransformTool = () =>
// Save button // Save button
const saveSelectionButton = document.createElement("button"); const saveSelectionButton = document.createElement("button");
saveSelectionButton.classList.add("button", "tool"); saveSelectionButton.classList.add("button", "tool");
saveSelectionButton.textContent = "Save"; saveSelectionButton.textContent = "Save Sel.";
saveSelectionButton.title = "Saves Selection"; saveSelectionButton.title = "Saves Selection";
saveSelectionButton.onclick = () => { saveSelectionButton.onclick = () => {
downloadCanvas({ downloadCanvas({
@ -655,25 +701,72 @@ const selectTransformTool = () =>
actionArray.appendChild(saveSelectionButton); actionArray.appendChild(saveSelectionButton);
actionArray.appendChild(createResourceButton); actionArray.appendChild(createResourceButton);
// Some useful actions to do with selection
const visibleActionArray = document.createElement("div");
visibleActionArray.classList.add("button-array");
// Save Visible button
const saveVisibleSelectionButton = document.createElement("button");
saveVisibleSelectionButton.classList.add("button", "tool");
saveVisibleSelectionButton.textContent = "Save Vis.";
saveVisibleSelectionButton.title = "Saves Visible Selection";
saveVisibleSelectionButton.onclick = () => {
const canvas = uil.getVisible(state.selected, {
categories: ["image", "user", "select-display"],
});
downloadCanvas({
cropToContent: false,
canvas,
});
};
// Save Visible as Resource Button
const createVisibleResourceButton = document.createElement("button");
createVisibleResourceButton.classList.add("button", "tool");
createVisibleResourceButton.textContent = "Vis. to Res.";
createVisibleResourceButton.title =
"Saves Visible Selection as a Resource";
createVisibleResourceButton.onclick = () => {
const canvas = uil.getVisible(state.selected, {
categories: ["image", "user", "select-display"],
});
const image = document.createElement("img");
image.src = canvas.toDataURL();
image.onload = () => {
tools.stamp.state.addResource("Selection Resource", image);
tools.stamp.enable();
};
};
visibleActionArray.appendChild(saveVisibleSelectionButton);
visibleActionArray.appendChild(createVisibleResourceButton);
// Disable buttons (if nothing is selected) // Disable buttons (if nothing is selected)
state.ctxmenu.disableButtons = () => { state.ctxmenu.disableButtons = () => {
saveSelectionButton.disabled = true; saveSelectionButton.disabled = true;
createResourceButton.disabled = true; createResourceButton.disabled = true;
saveVisibleSelectionButton.disabled = true;
createVisibleResourceButton.disabled = true;
}; };
// Disable buttons (if something is selected) // Disable buttons (if something is selected)
state.ctxmenu.enableButtons = () => { state.ctxmenu.enableButtons = () => {
saveSelectionButton.disabled = ""; saveSelectionButton.disabled = "";
createResourceButton.disabled = ""; createResourceButton.disabled = "";
saveVisibleSelectionButton.disabled = "";
createVisibleResourceButton.disabled = "";
}; };
state.ctxmenu.actionArray = actionArray; state.ctxmenu.actionArray = actionArray;
state.ctxmenu.visibleActionArray = visibleActionArray;
} }
menu.appendChild(state.ctxmenu.snapToGridLabel); menu.appendChild(state.ctxmenu.snapToGridLabel);
menu.appendChild(document.createElement("br")); menu.appendChild(document.createElement("br"));
menu.appendChild(state.ctxmenu.keepAspectRatioLabel); menu.appendChild(state.ctxmenu.keepAspectRatioLabel);
menu.appendChild(document.createElement("br")); menu.appendChild(document.createElement("br"));
menu.appendChild(state.ctxmenu.useClipboardLabel); menu.appendChild(state.ctxmenu.useClipboardLabel);
menu.appendChild(state.ctxmenu.selectionPeekOpacitySlider);
menu.appendChild(state.ctxmenu.actionArray); menu.appendChild(state.ctxmenu.actionArray);
menu.appendChild(state.ctxmenu.visibleActionArray);
}, },
shortcut: "S", shortcut: "S",
} }

View file

@ -151,18 +151,23 @@ const stampTool = () =>
); );
const resourceWrapper = document.createElement("div"); const resourceWrapper = document.createElement("div");
resourceWrapper.id = `resource-${resource.id}`; resourceWrapper.id = `resource-${resource.id}`;
resourceWrapper.title = resource.name;
resourceWrapper.classList.add("resource", "list-item"); resourceWrapper.classList.add("resource", "list-item");
const resourceTitle = document.createElement("input"); const resourceTitle = document.createElement("input");
resourceTitle.value = resource.name; resourceTitle.value = resource.name;
resourceTitle.title = resource.name;
resourceTitle.style.pointerEvents = "none"; resourceTitle.style.pointerEvents = "none";
resourceTitle.addEventListener("change", () => { resourceTitle.addEventListener("change", () => {
resource.name = resourceTitle.value; resource.name = resourceTitle.value;
resource.dirty = true; resource.dirty = true;
resourceTitle.title = resourceTitle.value; resourceWrapper.title = resourceTitle.value;
syncResources(); syncResources();
}); });
resourceTitle.addEventListener("keyup", function (event) {
if (event.key === "Enter") {
resourceTitle.blur();
}
});
resourceTitle.addEventListener("blur", () => { resourceTitle.addEventListener("blur", () => {
resourceTitle.style.pointerEvents = "none"; resourceTitle.style.pointerEvents = "none";
@ -301,6 +306,7 @@ const stampTool = () =>
const vpc = viewport.canvasToView(x, y); const vpc = viewport.canvasToView(x, y);
uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height); uiCtx.clearRect(0, 0, uiCanvas.width, uiCanvas.height);
state.erasePrevCursor && state.erasePrevCursor();
uiCtx.save(); uiCtx.save();
@ -314,16 +320,7 @@ const stampTool = () =>
} }
// Draw current cursor location // Draw current cursor location
uiCtx.lineWidth = 3; state.erasePrevCursor = _tool._cursor_draw(x, y);
uiCtx.strokeStyle = "#FFF";
uiCtx.beginPath();
uiCtx.moveTo(vpc.x, vpc.y + 10);
uiCtx.lineTo(vpc.x, vpc.y - 10);
uiCtx.moveTo(vpc.x + 10, vpc.y);
uiCtx.lineTo(vpc.x - 10, vpc.y);
uiCtx.stroke();
uiCtx.restore(); uiCtx.restore();
}; };