Files
PrometheusDante 2910671fed feat: allow more color configs (#28)
Addition of custom coloring options for nodes' color context menu as well as groups' color context menu, allowing to color title and background independently.
2024-08-04 11:49:33 +02:00

612 lines
16 KiB
JavaScript

/**
* Coded with love by Failfa.st
* LICENSE: AGPL 3.0
* https://github.com/failfa-st/failfast-comfyui-extensions/blob/main/LICENSE
*
* Visit https://github.com/failfa-st/failfast-comfyui-extensions for more info
*
* Homepage: https://failfa.st
* GitHub: https://github.com/failfa-st
* Discord: https://discord.com/invite/m3TBB9XEkb
*/
import { app } from "/scripts/app.js";
import { $el } from "/scripts/ui.js";
function getColor(index, lengthOfItems, l = 0.5) {
// Normalize the index value to be between 0 and 360 for full spectrum of hue
const hue = Math.floor((index / lengthOfItems) * 360);
// Convert the hue to RGB using helper function
const [r, g, b] = hslToRgb(hue / 360, 0.3, l);
// Convert RGB to Hex
return rgbToHex(r, g, b);
}
function hslToRgb(h, s, l) {
let r, g, b;
if (s === 0) {
r = g = b = l; // Achromatic
} else {
const hue2rgb = function hue2rgb(p, q, t) {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
function rgbToHex(r, g, b) {
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
}
function hslToHex(h, s, l) {
const [r, g, b] = hslToRgb(h, s, l);
return rgbToHex(r, g, b);
}
function shadeHexColor(hex, amount = -0.2) {
// Remove the # symbol if it exists
if (hex.startsWith("#")) {
hex = hex.slice(1);
}
// Convert the hex values to decimal (base 10) integers
let r = parseInt(hex.slice(0, 2), 16);
let g = parseInt(hex.slice(2, 4), 16);
let b = parseInt(hex.slice(4, 6), 16);
// Apply the shade amount to each RGB component
r = Math.max(0, Math.min(255, r + amount * 100));
g = Math.max(0, Math.min(255, g + amount * 100));
b = Math.max(0, Math.min(255, b + amount * 100));
// Convert the updated RGB values back to HEX
return rgbToHex(r, g, b);
}
function getContrastColor(hexColor) {
// Remove the # symbol if it exists
if (hexColor.startsWith("#")) {
hexColor = hexColor.slice(1);
}
// Convert the hex values to decimal (base 10) integers
const r = parseInt(hexColor.slice(0, 2), 16) / 255;
const g = parseInt(hexColor.slice(2, 4), 16) / 255;
const b = parseInt(hexColor.slice(4, 6), 16) / 255;
// Calculate the relative luminance
const L = 0.2126 * r + 0.7152 * g + 0.0722 * b;
// Use the contrast ratio to determine the text color
return L > 0.179 ? "#000000" : "#FFFFFF";
}
function rainbowify(app) {
// Sort by position on canvas
const nodes = [...app.graph._nodes].sort((a, b) => {
if (a.pos[1] > b.pos[1]) {
return 1;
}
if (a.pos[1] < b.pos[1]) {
return -1;
}
return a.pos[0] - b.pos[0];
});
nodes.forEach((node, index) => {
node.bgcolor = getColor(index, nodes.length, 0.3);
node.color = shadeHexColor(node.bgcolor);
node.setDirtyCanvas(true, true);
});
// app.graph.change();
}
function uncolor(app) {
app.graph._nodes.forEach((node) => {
if (node.type.toLowerCase() === "note") {
const [h, s, l] = colors.note;
const bgcolor = hslToHex(h / 360, s, l);
node.bgcolor = bgcolor;
node.color = shadeHexColor(node.bgcolor);
} else {
node.bgcolor = hslToHex(0, 0, 0.3);
node.color = shadeHexColor(node.bgcolor);
}
node.setDirtyCanvas(true, true);
});
}
const colors = {
loader: [0, 0.4, 0.3],
clip: [20, 0.4, 0.3],
note: [40, 0.4, 0.3],
sampler: [60, 0.4, 0.3],
controlnet: [80, 0.4, 0.3],
vae: [100, 0.4, 0.3],
conditioning: [120, 0.4, 0.3],
latent: [140, 0.4, 0.3],
mask: [160, 0.4, 0.3],
image: [180, 0.4, 0.3],
style: [200, 0.4, 0.3],
primitive: [220, 0.4, 0.3],
gligen: [240, 0.4, 0.3],
};
function colorNode(node) {
const colorRef = Object.entries(colors).find(([key]) => {
return node.type.toLowerCase().includes(key);
});
if (colorRef) {
const [h, s, l] = colorRef[1];
const bgcolor = hslToHex(h / 360, s, l);
node.bgcolor = bgcolor;
node.color = shadeHexColor(node.bgcolor);
}
}
function colorByType(app) {
app.graph._nodes.forEach((node) => {
colorNode(node);
node.setDirtyCanvas(true, true);
});
}
function colorPositiveNegative(app) {
app.graph._nodes.forEach((node) => {
// const onPropertyChanged = node.onPropertyChanged;
// node.onPropertyChanged = function () {};
if (node.title.toLowerCase().includes("positive")) {
const bgcolor = hslToHex(120 / 360, 0.4, 0.3);
node.bgcolor = bgcolor;
node.color = shadeHexColor(node.bgcolor);
} else if (node.title.toLowerCase().includes("negative")) {
const bgcolor = hslToHex(0, 0.4, 0.3);
node.bgcolor = bgcolor;
node.color = shadeHexColor(node.bgcolor);
}
node.setDirtyCanvas(true, true);
});
}
/**
* Colors
*/
let afterChange;
let colorModeValue;
function invokeAfterChange() {
switch (colorModeValue) {
case 1:
uncolor(app);
break;
case 2:
rainbowify(app);
break;
case 3:
colorByType(app);
break;
default:
break;
}
if (getPosNegValue()) {
colorPositiveNegative(app);
}
return afterChange?.apply(this, arguments);
}
function setColorMode(value, app) {
colorModeValue = value;
switch (value) {
case 1:
uncolor(app);
break;
case 2:
rainbowify(app);
break;
case 3:
colorByType(app);
break;
default:
app.graph._nodes.forEach((node) => {
node.bgcolor = node._bgcolor ?? node.bgcolor;
node.color = node._color ?? node.color;
node.setDirtyCanvas(true, true);
});
break;
}
if (getPosNegValue()) {
colorPositiveNegative(app);
}
}
const colorModes = ["default", "plain", "rainbow", "type"];
const colorsName = "Failfast.colors";
function getPosNegValue() {
return (
window.localStorage.getItem(`Comfy.Settings.${colorsName}-posneg`) ===
"true"
);
}
let loading = false;
app.registerExtension({
name: colorsName,
async setup(app) {
const value = +(
window.localStorage.getItem(`Comfy.Settings.${colorsName}`) ?? "0"
);
app.graph._nodes.forEach((node) => {
node._bgcolor = node._bgcolor ?? node.bgcolor;
node._color = node._color ?? node.color;
});
setColorMode(value, app);
},
loadedGraphNode(node, app) {
node._bgcolor = node._bgcolor ?? node.bgcolor;
node._color = node._color ?? node.color;
if (!loading) {
loading = true;
setTimeout(function () {
loading = false;
const value = +(
window.localStorage.getItem(`Comfy.Settings.${colorsName}`) ?? "0"
);
setColorMode(value, app);
}, 500);
}
},
async init(app) {
afterChange = app.graph.afterChange;
app.graph.afterChange = invokeAfterChange;
const onMenuNodeColors = LGraphCanvas.onMenuNodeColors;
LGraphCanvas.onMenuNodeColors = function (value, options, e, menu, node) {
const response = onMenuNodeColors.apply(this, arguments);
const menuRoot = menu.current_submenu.root;
const isGroup = node instanceof LGraphGroup;
if (!isGroup) {
menuRoot.append(
$el("div.litemenu-entry.submenu", [
$el(
"label",
{
style: {
position: "relative",
overflow: "hidden",
display: "block",
paddingLeft: "4px",
borderLeft: "8px solid #222",
},
},
[
"Custom Title",
$el("input", {
type: "color",
value: node.bgcolor,
style: {
position: "absolute",
right: "200%",
},
oninput(event) {
// node.bgcolor = event.target.value;
node.color = shadeHexColor(event.target.value);
// TODO: check if we can adjust this
// node.title_color = getContrastColor(node.bgcolor);
node.setDirtyCanvas(true, true);
},
}),
],
),
]),
);
menuRoot.append(
$el("div.litemenu-entry.submenu", [
$el(
"label",
{
style: {
position: "relative",
overflow: "hidden",
display: "block",
paddingLeft: "4px",
borderLeft: "8px solid #222",
},
},
[
"Custom BG",
$el("input", {
type: "color",
value: node.bgcolor,
style: {
position: "absolute",
right: "200%",
},
oninput(event) {
node.bgcolor = event.target.value;
// node.color = shadeHexColor(node.bgcolor);
// TODO: check if we can adjust this
// node.title_color = getContrastColor(node.bgcolor);
node.setDirtyCanvas(true, true);
},
}),
],
),
]),
);
menuRoot.append(
$el("div.litemenu-entry.submenu", [
$el(
"label",
{
style: {
position: "relative",
overflow: "hidden",
display: "block",
paddingLeft: "4px",
borderLeft: "8px solid #222",
},
},
[
"Custom All",
$el("input", {
type: "color",
value: node.bgcolor,
style: {
position: "absolute",
right: "200%",
},
oninput(event) {
node.bgcolor = event.target.value;
node.color = shadeHexColor(node.bgcolor);
// TODO: check if we can adjust this
// node.title_color = getContrastColor(node.bgcolor);
node.setDirtyCanvas(true, true);
},
}),
],
),
]),
);
}
if (isGroup) {
menuRoot.append(
$el("div.litemenu-entry.submenu", [
$el(
"label",
{
style: {
position: "relative",
overflow: "hidden",
display: "block",
paddingLeft: "4px",
borderLeft: "8px solid #222",
},
},
[
"Color Group",
$el("input", {
type: "color",
value: node.bgcolor,
style: {
position: "absolute",
right: "200%",
},
oninput(event) {
node.bgcolor = event.target.value;
node.color = shadeHexColor(node.bgcolor);
// TODO: check if we can adjust this
// node.title_color = getContrastColor(node.bgcolor);
node.setDirtyCanvas(true, true);
},
}),
],
),
]),
);
menuRoot.append(
$el("div.litemenu-entry.submenu", [
$el(
"label",
{
style: {
position: "relative",
overflow: "hidden",
display: "block",
paddingLeft: "4px",
borderLeft: "8px solid #222",
},
},
[
"Color All Title",
$el("input", {
type: "color",
value: node.bgcolor,
style: {
position: "absolute",
right: "200%",
},
oninput(event) {
node.recomputeInsideNodes();
// node.bgcolor = event.target.value;
node.color = shadeHexColor(event.target.value);
node._nodes.forEach((node_) => {
// node_.bgcolor = node.bgcolor;
node_.color = node.color;
});
// TODO: check if we can adjust this
// node.title_color = getContrastColor(node.bgcolor);
node.setDirtyCanvas(true, true);
},
}),
],
),
]),
);
menuRoot.append(
$el("div.litemenu-entry.submenu", [
$el(
"label",
{
style: {
position: "relative",
overflow: "hidden",
display: "block",
paddingLeft: "4px",
borderLeft: "8px solid #222",
},
},
[
"Color All BG",
$el("input", {
type: "color",
value: node.bgcolor,
style: {
position: "absolute",
right: "200%",
},
oninput(event) {
node.recomputeInsideNodes();
node.bgcolor = event.target.value;
// node.color = shadeHexColor(node.bgcolor);
node._nodes.forEach((node_) => {
node_.bgcolor = node.bgcolor;
// node_.color = node.color;
});
// TODO: check if we can adjust this
// node.title_color = getContrastColor(node.bgcolor);
node.setDirtyCanvas(true, true);
},
}),
],
),
]),
);
menuRoot.append(
$el("div.litemenu-entry.submenu", [
$el(
"label",
{
style: {
position: "relative",
overflow: "hidden",
display: "block",
paddingLeft: "4px",
borderLeft: "8px solid #222",
},
},
[
"Color All",
$el("input", {
type: "color",
value: node.bgcolor,
style: {
position: "absolute",
right: "200%",
},
oninput(event) {
node.recomputeInsideNodes();
node.bgcolor = event.target.value;
node.color = shadeHexColor(node.bgcolor);
node._nodes.forEach((node_) => {
node_.bgcolor = node.bgcolor;
node_.color = node.color;
});
// TODO: check if we can adjust this
// node.title_color = getContrastColor(node.bgcolor);
node.setDirtyCanvas(true, true);
},
}),
],
),
]),
);
}
return response;
};
app.ui.settings.addSetting({
id: colorsName,
name: "Color Mode",
type(name, setter, value) {
return $el("tr", [
$el("td", [
$el("label", {
for: colorsName.replaceAll(".", "-"),
textContent: "Color Mode",
}),
]),
$el("td", [
$el(
"select",
{
id: colorsName.replaceAll(".", "-"),
onchange: (event) => {
setter(+event.target.value);
},
},
colorModes.map((mode, index) => {
return $el("option", {
textContent: mode,
value: index,
selected: index === +value,
});
}),
),
$el(
"label",
{ style: { display: "block", paddingTop: "0.5rem" } },
[
$el("span", { style: { paddingRight: "0.5rem" } }, [
"Positive / Negative",
]),
$el("input", {
type: "checkbox",
checked:
window.localStorage.getItem(
`Comfy.Settings.${colorsName}-posneg`,
) === "true",
onchange: (event) => {
window.localStorage.setItem(
`Comfy.Settings.${colorsName}-posneg`,
event.target.checked.toString(),
);
if (event.target.checked) {
colorPositiveNegative(app);
}
},
}),
],
),
]),
]);
},
tooltip:
"Automatic color modes for nodes. (positive / negative will respect those words in the title )",
defaultValue: 2,
onChange(value) {
setColorMode(value, app);
},
});
},
});