Merge branch 'master' of https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet into new_init_config_disabled

This commit is contained in:
AlekPet
2025-01-24 18:25:37 +03:00
14 changed files with 509 additions and 343 deletions
+36 -10
View File
@@ -72,7 +72,7 @@ let PreviewImageSize = PreviewImageSizeLS
const SUPPORTS_FORMAT = {
image: ["jpg", "jpeg", "bmp", "png", "gif", "tiff", "avif"],
video: ["mp4", "webm"],
audio: ["ogg", "wav", "mp3"],
audio: ["ogg", "wav", "mp3", "webm"],
};
const loadingContent = (src) => {
@@ -86,15 +86,21 @@ const loadingContent = (src) => {
img.onload = (e) => res({ raw: img, type: "image", src });
img.onerror = (err) => rej(err);
img.src = src;
} else if (SUPPORTS_FORMAT.video.includes(ext)) {
}
if (SUPPORTS_FORMAT.video.includes(ext)) {
const video = document.querySelector(".preview_vid");
video.onerror = (err) => rej(err);
video.addEventListener("canplay", (e) => {
res({ raw: video, type: "video", src });
if (video.videoWidth !== 0) {
res({ raw: video, type: "video", src });
}
});
video.src = src;
} else if (SUPPORTS_FORMAT.audio.includes(ext)) {
}
if (SUPPORTS_FORMAT.audio.includes(ext)) {
const audio = document.querySelector(".preview_audio");
audio.onerror = (err) => rej(err);
@@ -139,9 +145,9 @@ app.registerExtension({
style: {
position: "absolute",
maxWidth: "160px",
right: "calc(-1% - 160px)",
zIndex: 9999999,
},
parent: this.root,
parent: document.body,
},
[
$el("canvas.preview_img", {
@@ -168,6 +174,16 @@ app.registerExtension({
this.root.preview_content_combo = preview_content_combo;
}
const closeItem = LiteGraph.ContextMenu.prototype.close;
LiteGraph.ContextMenu.prototype.close = function () {
closeItem?.apply(this, arguments);
if (this.root?.preview_content_combo) {
this.root.preview_content_combo.remove();
this.root.preview_content_combo = null;
}
};
LiteGraph.pointerListenerAdd(element, "enter", (e) => {
if (element?.dataset?.value) {
const body_rect = document.body.getBoundingClientRect();
@@ -208,12 +224,12 @@ app.registerExtension({
root_rect.right + previewWidth > body_rect.width
) {
this.root.preview_content_combo.style.left = `${
-previewWidth - 10
root_rect.left - previewWidth - 10
}px`;
this.root.preview_content_combo.style.right = "auto";
} else {
this.root.preview_content_combo.style.left = "auto";
this.root.preview_content_combo.style.right = `calc(-1% - ${this.root.preview_content_combo.style.maxWidth})`;
this.root.preview_content_combo.style.left = `${
root_rect.left + root_rect.width + 10
}px`;
}
ctx.clearRect(0, 0, canvas.width, canvas.height);
@@ -268,6 +284,16 @@ app.registerExtension({
});
};
const originalCloseAllContextMenus = LiteGraph.closeAllContextMenus;
LiteGraph.closeAllContextMenus = function () {
originalCloseAllContextMenus?.apply(this, arguments);
const previewCombo = document.querySelector(".preview_content_combo");
if (previewCombo) {
previewCombo.remove();
}
};
// PreviewImage settings ui
app.ui.settings.addSetting({
id: `${idExt}.PreviewImage`,
+15 -5
View File
@@ -118,6 +118,13 @@ function animateTransitionProps(
preStyles = { display: "block" }
) {
Object.assign(el.style, preStyles);
el.style.transition =
!el.style.transition ||
!window.getComputedStyle(el).getPropertyValue("transition")
? "all .8s"
: el.style.transition;
return new Promise((res) => {
setTimeout(() => {
Object.assign(el.style, props);
@@ -145,9 +152,12 @@ function animateClick(target, params = {}) {
const hide = +target.style.opacity === 0;
return animateTransitionProps(target, {
opacity: hide ? opacityVal : 0,
})
.then(() => showHide({ elements: [target], hide: !hide }))
.then(() => callback());
}).then((el) => {
const isHide = hide || el.style.display === "none";
showHide({ elements: [target], hide: !hide });
callback();
return isHide;
});
}
function showHide({ elements = [], hide = null, displayProp = "block" } = {}) {
@@ -442,8 +452,8 @@ function createWindowModal({
switch (typeof text) {
case "string":
if (/^\<*.\/?\>$/.test(text)) {
parent.innerHTML(text);
if (/^\<.*\/?\>$/.test(text)) {
parent.innerHTML = text;
} else {
parent.textContent = text;
}
+21
View File
@@ -113,3 +113,24 @@ npm run brushes
- Update image (not update the images of the node with piping in image (**Note:** you must enable this option so that the mask does not change))
![Screenshot Piping](https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet/raw/master/PainterNode/pipingImage.jpg)
# User custom fonts
> To add custom fonts, they must be written in the css file: `PainterNode\css\painter_node_user_fonts.css`
### Two variants:
**Variant 1**: Path to fonts, fonts puts in the `PainterNode\fonts`, adn add to the code in the css.
```css
@font-face {
font-family: "Pricedown";
src: url("./../../fonts/painternode/pricedown.otf") format("opentype");
}
```
**Variant 2**: Add url to font in the `painter_node_user_fonts.css`, example used link [google fonts.](https://fonts.google.com/)
```css
@import url("https://fonts.googleapis.com/css2?family=Jacquard+12&display=swap");
```
+5
View File
@@ -0,0 +1,5 @@
/* Default styles */
@import url("https://fonts.googleapis.com/css2?family=Jacquard+12&display=swap");
/* User fonts */
@import url("./painter_node_user_fonts.css");
@@ -0,0 +1,27 @@
/* Example add font file for different formats, and supports old browsers: */
/*
@font-face {
font-family: 'MyNameFont';
src: url('webfont.eot');
src: url('webfont.eot?#iefix') format('embedded-opentype'),
url('webfont.woff') format('woff'),
url('webfont.ttf') format('truetype'),
url("webfont.otf") format("opentype");
url('webfont.svg#svgFontName') format('svg');
}
*/
/* User fonts files */
/* Pricedown font */
@font-face {
font-family: "Pricedown";
src: url("./../../fonts/painternode/pricedown.otf") format("opentype");
}
/* Harry_font__YOURSSINCERELYLARRY_ font */
@font-face {
font-family: "Harry_font__YOURSSINCERELYLARRY_";
src: url("./../../fonts/painternode/HarryFontYourssincerelylarry.ttf")
format("truetype");
}
Binary file not shown.
Binary file not shown.
+129 -41
View File
@@ -22,6 +22,7 @@ import {
isEmptyObject,
THEMES_MODAL_WINDOW,
} from "./utils.js";
import "./lib/painternode/fontfaceobserver.js";
import { MyPaintManager } from "./lib/painternode/manager_mypaint.js";
// ================= FUNCTIONS ================
@@ -80,6 +81,30 @@ function resizeCanvas(node, sizes) {
app.graph.setDirtyCanvas(true, false);
}, 1000);
}
const FONTS = {};
const STATES = {
fontsLoaded: false,
};
async function getLoadedFonts() {
const getListFonts = async () => {
const fontFaces = await document.fonts.ready;
const loadedFonts = [];
document.fonts.forEach((font) => {
loadedFonts.push(font.family);
});
return [...new Set(loadedFonts)];
};
await getListFonts().then((fonts) => {
fonts.forEach((font) => {
FONTS[font] = { type: "custom" };
});
});
}
// ================= END FUNCTIONS ================
// ================= CLASS PAINTER ================
@@ -110,13 +135,14 @@ class Painter {
// this.redo_history = this.node.LS_Cls.LS_Painters.redo_history || [];
this.fonts = {
Arial: "arial",
"Times New Roman": "Times New Roman",
Verdana: "verdana",
Georgia: "georgia",
Courier: "courier",
"Comic Sans MS": "comic sans ms",
Impact: "impact",
Arial: { type: "default" },
"Times New Roman": { type: "default" },
Verdana: { type: "default" },
Georgia: { type: "default" },
Courier: { type: "default" },
"Comic Sans MS": { type: "default" },
Impact: { type: "default" },
...FONTS,
};
this.bringFrontSelected = true;
@@ -372,6 +398,7 @@ class Painter {
});
this.painter_bg_setting.appendChild(this.bgImageFile);
this.changePropertyBrush();
this.createBrushesToolbar();
this.bindEvents();
@@ -980,29 +1007,71 @@ class Painter {
});
const separator = makeElement("div", { class: ["separator"] });
const selectFontFamily = makeElement("select", {
dataset: { prop: "prop_fontFamily" },
class: ["font_family_select"],
});
for (let f in this.fonts) {
for (let font in this.fonts) {
const option = makeElement("option");
if (f === "Arial") option.setAttribute("selected", true);
option.value = this.fonts[f];
option.textContent = f;
if (font === "Arial") option.setAttribute("selected", true);
option.value = font;
option.textContent = font;
selectFontFamily.appendChild(option);
}
// Select front event
selectFontFamily.onchange = (e) => {
if (this.getActiveStyle("fontFamily") != selectFontFamily.value)
this.setActiveStyle("fontFamily", selectFontFamily.value);
if (this.getActiveStyle("fontFamily") != selectFontFamily.value) {
if (this.fonts[selectFontFamily.value].type == "default") {
this.setActiveStyle("fontFamily", selectFontFamily.value);
return;
}
const font = new FontFaceObserver(selectFontFamily.value);
font.load().then(
() => {
// console.log("Font is available");
this.setActiveStyle("fontFamily", selectFontFamily.value);
},
() => {
// console.log("Font not is available");
}
);
this.uploadPaintFile(this.node.name);
}
};
const infoFontsButton = makeElement("button", {
style: {},
textContent: "?",
title: "Info for fonts",
onclick: (e) =>
createWindowModal({
textTitle: "NOTE",
textBody:
"<b>If fonts not loaded in the canvas, refresh page browser!😅</b>",
...THEMES_MODAL_WINDOW.warning,
options: {
auto: { autohide: true, autoshow: true, autoremove: true },
parent: this.canvas.wrapperEl,
overlay: {
overlay_enabled: true,
overlayStyles: {
position: "absolute",
},
},
},
}),
});
property_textbox.append(
buttonItalic,
buttonBold,
buttonUnderline,
separator,
selectFontFamily
selectFontFamily,
infoFontsButton
);
this.painter_drawning_box_property.append(property_textbox);
}
@@ -1577,12 +1646,20 @@ class Painter {
if (!targets || targets.length == 0) return;
// Selected tools
const setProps = (style, check) => {
const setProps = (style, value) => {
const propEl = this.painter_drawning_box_property.querySelector(
`#prop_${style}`
`[data-prop=prop_${style}]`
);
if (propEl) propEl.classList[check ? "remove" : "add"]("active");
if (propEl) {
switch (propEl.dataset.prop) {
case "prop_fontFamily": {
propEl.value = value;
}
default:
propEl.classList[value ? "remove" : "add"]("active");
}
}
};
targets.forEach((target) => {
@@ -1602,6 +1679,8 @@ class Painter {
"underline",
Boolean(this.getActiveStyle("underline", target)) == false
);
setProps("fontFamily", this.getActiveStyle("fontFamily", target));
}
if (
@@ -1991,6 +2070,29 @@ class Painter {
);
}
getImageByName(image_name) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = (e) => resolve(new Error("Image not load!"));
let name = image_name;
let folder_separator = name.lastIndexOf("/");
let subfolder = "";
if (folder_separator > -1) {
subfolder = name.substring(0, folder_separator);
name = name.substring(folder_separator + 1);
}
img.src = api.apiURL(
`/view?filename=${encodeURIComponent(
name
)}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}`
);
});
}
async addImageToCanvas(image, options = {}) {
async function uploadFile(file) {
try {
@@ -2007,28 +2109,7 @@ class Painter {
let path = data.name;
if (data.subfolder) path = data.subfolder + "/" + path;
const img = await new Promise((resolve, reject) => {
let img = new Image();
img.onload = () => resolve(img);
img.onerror = (e) => resolve(new Error("Image not load!"));
let name = path;
let folder_separator = name.lastIndexOf("/");
let subfolder = "";
if (folder_separator > -1) {
subfolder = name.substring(0, folder_separator);
name = name.substring(folder_separator + 1);
}
img.src = api.apiURL(
`/view?filename=${encodeURIComponent(
name
)}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}`
);
});
return img;
return await getImageByName(path);
} else {
console.log(`${resp.status} - ${resp.statusText}`);
return resp.statusText;
@@ -2106,7 +2187,7 @@ class Painter {
await new Promise((res) => {
const uploadFile = async (blobFile) => {
try {
const resp = await fetch("/upload/image", {
const resp = await api.fetchApi("/upload/image", {
method: "POST",
body: blobFile,
});
@@ -2528,6 +2609,7 @@ app.registerExtension({
async init(app) {
// Add styles
addStylesheet("css/painternode/painter_node_styles.css", import.meta.url);
addStylesheet("css/painternode/painter_node_fonts.css", import.meta.url);
// Add settings params painter node
app.ui.settings.addSetting({
@@ -2616,6 +2698,13 @@ app.registerExtension({
},
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "PainterNode") {
if (!STATES.fontsLoaded) {
await getLoadedFonts().then(() => {
STATES.fontsLoaded = true;
console.log("PainterNode: Loading fonts completed");
});
}
// Create node
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = async function () {
@@ -2689,7 +2778,6 @@ app.registerExtension({
const getExtraMenuOptions = nodeType.prototype.getExtraMenuOptions;
nodeType.prototype.getExtraMenuOptions = async function (_, options) {
getExtraMenuOptions?.apply(this, arguments);
await this.getTitle();
const past_index = options.findIndex(
(m) => m?.content === "Paste (Clipspace)"
+8
View File
@@ -0,0 +1,8 @@
/* Font Face Observer v2.3.0 - © Bram Stein. License: BSD-3-Clause */(function(){function p(a,c){document.addEventListener?a.addEventListener("scroll",c,!1):a.attachEvent("scroll",c)}function u(a){document.body?a():document.addEventListener?document.addEventListener("DOMContentLoaded",function b(){document.removeEventListener("DOMContentLoaded",b);a()}):document.attachEvent("onreadystatechange",function g(){if("interactive"==document.readyState||"complete"==document.readyState)document.detachEvent("onreadystatechange",g),a()})};function w(a){this.g=document.createElement("div");this.g.setAttribute("aria-hidden","true");this.g.appendChild(document.createTextNode(a));this.h=document.createElement("span");this.i=document.createElement("span");this.m=document.createElement("span");this.j=document.createElement("span");this.l=-1;this.h.style.cssText="max-width:none;display:inline-block;position:absolute;height:100%;width:100%;overflow:scroll;font-size:16px;";this.i.style.cssText="max-width:none;display:inline-block;position:absolute;height:100%;width:100%;overflow:scroll;font-size:16px;";
this.j.style.cssText="max-width:none;display:inline-block;position:absolute;height:100%;width:100%;overflow:scroll;font-size:16px;";this.m.style.cssText="display:inline-block;width:200%;height:200%;font-size:16px;max-width:none;";this.h.appendChild(this.m);this.i.appendChild(this.j);this.g.appendChild(this.h);this.g.appendChild(this.i)}
function x(a,c){a.g.style.cssText="max-width:none;min-width:20px;min-height:20px;display:inline-block;overflow:hidden;position:absolute;width:auto;margin:0;padding:0;top:-999px;white-space:nowrap;font-synthesis:none;font:"+c+";"}function B(a){var c=a.g.offsetWidth,b=c+100;a.j.style.width=b+"px";a.i.scrollLeft=b;a.h.scrollLeft=a.h.scrollWidth+100;return a.l!==c?(a.l=c,!0):!1}function C(a,c){function b(){var e=g;B(e)&&null!==e.g.parentNode&&c(e.l)}var g=a;p(a.h,b);p(a.i,b);B(a)};function D(a,c,b){c=c||{};b=b||window;this.family=a;this.style=c.style||"normal";this.weight=c.weight||"normal";this.stretch=c.stretch||"normal";this.context=b}var E=null,F=null,G=null,H=null;function I(a){null===F&&(M(a)&&/Apple/.test(window.navigator.vendor)?(a=/AppleWebKit\/([0-9]+)(?:\.([0-9]+))(?:\.([0-9]+))/.exec(window.navigator.userAgent),F=!!a&&603>parseInt(a[1],10)):F=!1);return F}function M(a){null===H&&(H=!!a.document.fonts);return H}
function N(a,c){var b=a.style,g=a.weight;if(null===G){var e=document.createElement("div");try{e.style.font="condensed 100px sans-serif"}catch(q){}G=""!==e.style.font}return[b,g,G?a.stretch:"","100px",c].join(" ")}
D.prototype.load=function(a,c){var b=this,g=a||"BESbswy",e=0,q=c||3E3,J=(new Date).getTime();return new Promise(function(K,L){if(M(b.context)&&!I(b.context)){var O=new Promise(function(r,t){function h(){(new Date).getTime()-J>=q?t(Error(""+q+"ms timeout exceeded")):b.context.document.fonts.load(N(b,'"'+b.family+'"'),g).then(function(n){1<=n.length?r():setTimeout(h,25)},t)}h()}),P=new Promise(function(r,t){e=setTimeout(function(){t(Error(""+q+"ms timeout exceeded"))},q)});Promise.race([P,O]).then(function(){clearTimeout(e);
K(b)},L)}else u(function(){function r(){var d;if(d=-1!=k&&-1!=l||-1!=k&&-1!=m||-1!=l&&-1!=m)(d=k!=l&&k!=m&&l!=m)||(null===E&&(d=/AppleWebKit\/([0-9]+)(?:\.([0-9]+))/.exec(window.navigator.userAgent),E=!!d&&(536>parseInt(d[1],10)||536===parseInt(d[1],10)&&11>=parseInt(d[2],10))),d=E&&(k==y&&l==y&&m==y||k==z&&l==z&&m==z||k==A&&l==A&&m==A)),d=!d;d&&(null!==f.parentNode&&f.parentNode.removeChild(f),clearTimeout(e),K(b))}function t(){if((new Date).getTime()-J>=q)null!==f.parentNode&&f.parentNode.removeChild(f),
L(Error(""+q+"ms timeout exceeded"));else{var d=b.context.document.hidden;if(!0===d||void 0===d)k=h.g.offsetWidth,l=n.g.offsetWidth,m=v.g.offsetWidth,r();e=setTimeout(t,50)}}var h=new w(g),n=new w(g),v=new w(g),k=-1,l=-1,m=-1,y=-1,z=-1,A=-1,f=document.createElement("div");f.dir="ltr";x(h,N(b,"sans-serif"));x(n,N(b,"serif"));x(v,N(b,"monospace"));f.appendChild(h.g);f.appendChild(n.g);f.appendChild(v.g);b.context.document.body.appendChild(f);y=h.g.offsetWidth;z=n.g.offsetWidth;A=v.g.offsetWidth;t();
C(h,function(d){k=d;r()});x(h,N(b,'"'+b.family+'",sans-serif'));C(n,function(d){l=d;r()});x(n,N(b,'"'+b.family+'",serif'));C(v,function(d){m=d;r()});x(v,N(b,'"'+b.family+'",monospace'))})})};"object"===typeof module?module.exports=D:(window.FontFaceObserver=D,window.FontFaceObserver.prototype.load=D.prototype.load);}());
+11
View File
@@ -1,3 +1,11 @@
.canvas_container_openpose {
width: auto !important;
height: auto !important;
}
.canvas_container_openpose canvas {
width: 100% !important;
height: auto !important;
}
.pose_panelButtons {
position: absolute;
padding: 4px;
@@ -6,6 +14,9 @@
flex-direction: column;
width: fit-content;
}
.pose_panelButtons button {
width: 32px;
}
.pose_panelButtons .clear_history {
border-color: var(--error-text);
color: var(--error-text) !important;
+255 -285
View File
@@ -2,10 +2,10 @@
* Title: Set Poses in ComflyUI from ControlNet
* Author: AlekPet
* Description: I rewrote the main.js file as a class, from fkunn1326's openpose-editor (https://github.com/fkunn1326/openpose-editor/blob/master/javascript/main.js)
* Version: 2024.10.13
* Github: https://github.com/AlekPet/ComfyUI_Custom_Nodes_AlekPet
*/
import { api } from "../../scripts/api.js";
import { app } from "../../scripts/app.js";
import { $el } from "../../scripts/ui.js";
import { addStylesheet } from "../../scripts/utils.js";
@@ -78,19 +78,16 @@ const default_keypoints = [
[260, 72],
];
window.LS_Poses = {};
function LS_Save() {
localStorage.setItem("ComfyUI_Poses", JSON.stringify(LS_Poses));
}
function resizeCanvas(node, sizes) {
const { width, height } = sizes ?? node.openPose.currentCanvasSize;
const { width, height } = sizes ?? node.openPose.settings.currentCanvasSize;
node.openPose.canvas.setDimensions({
width: width,
height: height,
});
node.openPose.canvas.setDimensions(
{
width: width,
height: height,
},
{ cssOnly: false, backstoreOnly: true }
);
node.openPose.canvas.getElement().width = width;
node.openPose.canvas.getElement().height = height;
@@ -101,18 +98,26 @@ function resizeCanvas(node, sizes) {
class OpenPose {
constructor(node, canvasElement) {
this.lockMode = false;
this.visibleEyes = true;
this.flipped = false;
this.node = node;
this.undo_history = LS_Poses[node.name].undo_history || [];
this.redo_history = LS_Poses[node.name].redo_history || [];
this.history_change = false;
this.currentCanvasSize = { width: 512, height: 512 };
this.maxSizeNodeW = 1024;
this.maxSizehNodeH = 200;
this.settings = {
undo_history: [],
redo_history: [],
currentCanvasSize: { width: 512, height: 512 },
background: null,
};
this.canvas = this.initCanvas(canvasElement);
this.image = node.widgets.find((w) => w.name === "image");
}
getSettings() {
return JSON.parse(JSON.stringify(this.settings));
}
setCanvasSize(new_width, new_height, resetPose = false) {
resizeCanvas(this.node, {
@@ -120,20 +125,13 @@ class OpenPose {
height: new_height,
});
this.currentCanvasSize = { width: new_width, height: new_height };
// Check isset local storage key currentCanvasSize
if (!LS_Poses[this.node.name].hasOwnProperty("currentCanvasSize"))
LS_Poses[this.node.name]["currentCanvasSize"] = {};
LS_Poses[this.node.name]["currentCanvasSize"] = this.currentCanvasSize;
this.settings.currentCanvasSize = { width: new_width, height: new_height };
this.node.title = `${this.node.type} - ${new_width}x${new_height}`;
resetPose && this.resetCanvas();
this.canvas.renderAll();
app.graph.setDirtyCanvas(true, false);
this.node.onResize();
LS_Save();
}
setPose(keypoints) {
@@ -248,6 +246,9 @@ class OpenPose {
this.canvas = new fabric.Canvas(this.canvas, {
backgroundColor: "#000",
preserveObjectStacking: true,
containerClass: "canvas_container_openpose",
width: this.settings.currentCanvasSize.width,
height: this.settings.currentCanvasSize.height,
});
const updateLines = (target) => {
@@ -354,68 +355,68 @@ class OpenPose {
});
this.canvas.on("object:modified", (e) => {
if (
this.lockMode ||
this.canvas.getActiveObject().type == "activeSelection"
) {
if (this.canvas.getActiveObject().type == "activeSelection") {
this.uploadPoseFile(this.node.name);
return;
}
this.undo_history.push(this.getJSON());
this.redo_history.length = 0;
this.history_change = true;
this.settings.undo_history.push(this.getJSON());
this.settings.redo_history.length = 0;
this.uploadPoseFile(this.node.name);
});
if (!LS_Poses[this.node.name].undo_history.length) {
if (!this.settings.undo_history.length) {
this.setPose(default_keypoints);
this.undo_history.push(this.getJSON());
this.settings.undo_history.push(this.getJSON());
}
return this.canvas;
}
undo() {
if (this.undo_history.length > 0) {
this.lockMode = true;
if (this.undo_history.length > 1)
this.redo_history.push(this.undo_history.pop());
if (this.settings.undo_history.length > 0) {
if (this.settings.undo_history.length > 1)
this.settings.redo_history.push(this.settings.undo_history.pop());
const content = this.undo_history[this.undo_history.length - 1];
const content =
this.settings.undo_history[this.settings.undo_history.length - 1];
this.loadPreset(content);
this.canvas.renderAll();
this.lockMode = false;
this.history_change = true;
this.uploadPoseFile(this.node.name);
}
}
redo() {
if (this.redo_history.length > 0) {
this.lockMode = true;
const content = this.redo_history.pop();
this.undo_history.push(content);
if (this.settings.redo_history.length > 0) {
const content = this.settings.redo_history.pop();
this.settings.undo_history.push(content);
this.loadPreset(content);
this.canvas.renderAll();
this.lockMode = false;
this.history_change = true;
this.uploadPoseFile(this.node.name);
}
}
resetCanvas(reset_size = false) {
if (reset_size && confirm("Reset canvas size?")) {
this.setCanvasSize(512, 512);
if (reset_size) {
if (confirm("Clear undo/redo?")) {
this.settings.undo_history = [];
this.settings.redo_history = [];
}
if (confirm("Reset canvas size?")) {
this.setCanvasSize(512, 512);
const backgroundImg = this.canvas.backgroundImage;
this.canvas.clear();
this.canvas.backgroundColor = "#000";
this.addPose();
this.setBackground(backgroundImg);
this.uploadPoseFile(this.node.name);
}
}
const backgroundImg = this.canvas.backgroundImage;
this.canvas.clear();
this.canvas.backgroundColor = "#000";
this.addPose();
this.setBackground(backgroundImg);
this.uploadPoseFile(this.node.name);
}
setBackground(backgroundImg) {
@@ -431,15 +432,6 @@ class OpenPose {
}
}
updateHistoryData() {
if (this.history_change) {
LS_Poses[this.node.name].undo_history = this.undo_history;
LS_Poses[this.node.name].redo_history = this.redo_history;
LS_Save();
this.history_change = false;
}
}
uploadPoseFile(fileName) {
// Upload pose to temp folder ComfyUI
const hideShowControls = (show) => {
@@ -456,44 +448,33 @@ class OpenPose {
hideShowControls(false);
const uploadFile = async (blobFile) => {
try {
const resp = await fetch("/upload/image", {
method: "POST",
body: blobFile,
});
if (resp.status === 200) {
const data = await resp.json();
if (!this.image.options.values.includes(data.name)) {
this.image.options.values.push(data.name);
}
this.image.value = data.name;
this.updateHistoryData();
hideShowControls(true);
} else {
alert(resp.status + " - " + resp.statusText);
}
} catch (error) {
hideShowControls(true);
console.log(error);
}
};
//Temporarily hide background image
let tmp_BackgroundImg = this.canvas.backgroundImage;
this.canvas.backgroundImage = null;
this.canvas.renderAll();
this.canvas.lowerCanvasEl.toBlob(function (blob) {
let formData = new FormData();
this.canvas.lowerCanvasEl.toBlob((blob) => {
const formData = new FormData();
formData.append("image", blob, fileName);
formData.append("overwrite", "true");
formData.append("type", "input");
uploadFile(formData);
this.uploadFileToServer(formData)
.then((jsonData) => {
if (!jsonData.success) {
throw new Error(jsonData.error);
}
const { name } = jsonData.data;
if (!this.image.options.values.includes(name)) {
this.image.options.values.push(name);
}
this.image.value = name;
hideShowControls(true);
})
.catch(() => hideShowControls(true));
}, "image/png");
// - end
@@ -510,26 +491,80 @@ class OpenPose {
};
}
async uploadFileToServer(formData) {
try {
const resp = await api.fetchApi("/upload/image", {
method: "POST",
body: formData,
});
if (resp.status !== 200) {
return { success: false, error: `${resp.status} - ${resp.statusText}` };
}
return { success: true, data: await resp.json() };
} catch (error) {
return { success: false, error };
}
}
getImageUrlByName(jsonData) {
return {
url: api.apiURL(
`/view?filename=${encodeURIComponent(
jsonData.name
)}&type=input&subfolder=${
jsonData.subfolder
}${app.getPreviewFormatParam()}${app.getRandParam()}`
),
name: jsonData.name,
subfolder: jsonData.subfolder,
};
}
onLoadBackground(e) {
try {
const file = this.backgroundInput.files[0];
const url = URL.createObjectURL(file);
fabric.Image.fromURL(url, (img) => {
img.set({
originX: "left",
originY: "top",
opacity: 0.5,
});
this.canvas.setBackgroundImage(
img,
this.canvas.renderAll.bind(this.canvas),
{
scaleX: this.canvas.width / img.width,
scaleY: this.canvas.height / img.height,
const formData = new FormData();
formData.append("image", file);
formData.append("overwrite", "true");
formData.append("type", "input");
this.uploadFileToServer(formData)
.then((jsonData) => {
if (!jsonData.success) {
throw new Error(jsonData.error);
}
);
});
} catch (err) {}
return this.getImageUrlByName(jsonData.data);
})
.then((image_data) => {
if (!image_data) {
console.log("Invalid data image!");
return;
}
this.settings.background = image_data.url;
fabric.Image.fromURL(image_data.url, (img) => {
img.set({
originX: "left",
originY: "top",
opacity: 0.5,
});
this.canvas.setBackgroundImage(
img,
this.canvas.renderAll.bind(this.canvas),
{
scaleX: this.canvas.width / img.width,
scaleY: this.canvas.height / img.height,
}
);
});
});
} catch (err) {
console.error(err);
}
}
getJSON() {
@@ -563,84 +598,20 @@ class OpenPose {
// Create OpenPose widget
function createOpenPose(node, inputName, inputData, app) {
node.name = inputName;
const widget = {
type: "openpose",
name: `w${inputName}`,
draw: function (ctx, _, widgetWidth, y, widgetHeight) {
const margin = 10,
visible = app.canvas.ds.scale > 0.5 && this.type === "openpose",
clientRectBound = ctx.canvas.getBoundingClientRect(),
transform = new DOMMatrix()
.scaleSelf(
clientRectBound.width / ctx.canvas.width,
clientRectBound.height / ctx.canvas.height
)
.multiplySelf(ctx.getTransform())
.translateSelf(margin, margin + y),
w = (widgetWidth - margin * 2 - 3) * transform.a;
let aspect_ratio = 1;
if (node.openPose.canvas.width !== node.openPose.canvas.height) {
aspect_ratio = node.openPose.canvas.width / node.openPose.canvas.height;
}
Object.assign(this.openpose.style, {
left: `${transform.a * margin + transform.e + clientRectBound.left}px`,
top: `${transform.d + transform.f + clientRectBound.top}px`,
width: w + "px",
height: w / aspect_ratio + "px",
position: "absolute",
zIndex: app.graph._nodes.indexOf(node),
});
Object.assign(this.openpose.children[0].style, {
width: w + "px",
height: w / aspect_ratio + "px",
});
Object.assign(this.openpose.children[1].style, {
width: w + "px",
height: w / aspect_ratio + "px",
});
Array.from(this.openpose.children[2].children).forEach((element) => {
Object.assign(element.style, {
width: `${28.0 * transform.a}px`,
height: `${22.0 * transform.d}px`,
fontSize: `${transform.d * 10.0}px`,
});
if (element.classList.contains("posenode_cnavas_size")) {
const svg = element.children[0];
Object.assign(element.style, {
height: `${28.0 * transform.d}px`,
});
Object.assign(svg.style, {
width: `${20.0 * transform.a}px`,
height: `${20.0 * transform.d}px`,
});
}
element.hidden = !visible;
});
},
};
// Fabric canvas
let canvasOpenPose = document.createElement("canvas");
node.openPose = new OpenPose(node, canvasOpenPose);
const openPoseWrapper = $el("div.openPoseWrapper");
const canvasOpenPose = $el("canvas");
node.openPoseWrapper = openPoseWrapper;
node.openPose.canvas.setWidth(512);
node.openPose.canvas.setHeight(512);
// Add buttons add, reset, undo, redo poses
node.addWidget("button", "Add pose", "add_pose", () => {
node.openPose.addPose();
});
let widgetCombo = node.widgets.filter((w) => w.type === "combo");
widgetCombo[0].value = node.name;
widget.openpose = node.openPose.canvas.wrapperEl;
widget.parent = node;
node.addWidget("button", "Reset pose", "reset_pose", () => {
node.openPose.resetCanvas(true);
});
// Create elements undo, redo, clear history
let panelButtons = $el("div.pose_panelButtons.comfy-menu-btns", [
@@ -710,8 +681,8 @@ function createOpenPose(node, inputName, inputData, app) {
},
}),
$el("button.posenode_cnavas_size", {
innerHTML: `<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 32 32">
<path d="m15.8,18.93q0,0.22 -0.17,0.4l-5.73,5.73l2.48,2.48q0.33,0.33 0.33,0.78t-0.33,0.78q-0.33,0.33 -0.78,0.33l-7.73,0q-0.45,0 -0.78,-0.33t-0.33,-0.78l0,-7.73q0,-0.45 0.33,-0.78t0.78,-0.33q0.45,0 0.78,0.33l2.48,2.48l5.73,-5.73q0.17,-0.17 0.4,-0.17t0.4,0.17l1.97,1.97q0.17,0.17 0.17,0.4zm13.47,-14.91l0,7.73q0,0.45 -0.33,0.78t-0.78,0.33q-0.45,0 -0.78,-0.33l-2.48,-2.48l-5.73,5.73q-0.17,0.17 -0.4,0.17t-0.4,-0.17l-1.97,-1.97q-0.17,-0.17 -0.17,-0.4t0.17,-0.4l5.73,-5.73l-2.48,-2.48q-0.33,-0.33 -0.33,-0.78t0.33,-0.78q0.33,-0.33 0.78,-0.33l7.73,0q0.45,0 0.78,0.33t0.33,0.78z" fill="currentColor" id="posenode_svg_canvas_size"/>
innerHTML: `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 32 32">
<path d="m15.8,18.93q0,0.22 -0.17,0.4l-5.73,5.73l2.48,2.48q0.33,0.33 0.33,0.78t-0.33,0.78q-0.33,0.33 -0.78,0.33l-7.73,0q-0.45,0 -0.78,-0.33t-0.33,-0.78l0,-7.73q0,-0.45 0.33,-0.78t0.78,-0.33q0.45,0 0.78,0.33l2.48,2.48l5.73,-5.73q0.17,-0.17 0.4,-0.17t0.4,0.17l1.97,1.97q0.17,0.17 0.17,0.4zm13.47,-14.91l0,7.73q0,0.45 -0.33,0.78t-0.78,0.33q-0.45,0 -0.78,-0.33l-2.48,-2.48l-5.73,5.73q-0.17,0.17 -0.4,0.17t-0.4,-0.17l-1.97,-1.97q-0.17,-0.17 -0.17,-0.4t0.17,-0.4l5.73,-5.73l-2.48,-2.48q-0.33,-0.33 -0.33,-0.78t0.33,-0.78q0.33,-0.33 0.78,-0.33l7.73,0q0.45,0 0.78,0.33t0.33,0.78z" fill="currentColor" id="posenode_svg_canvas_size"/>
</svg>`,
style: { margin: 0, padding: 0 },
title: "Change canvas size",
@@ -741,6 +712,24 @@ function createOpenPose(node, inputName, inputData, app) {
}),
]);
node.openPose = new OpenPose(node, canvasOpenPose);
const widget = node.addDOMWidget(
"widget_openpose",
"openpose",
openPoseWrapper,
{
getValue() {
return node.openPose.getSettings();
},
}
);
let widgetCombo = node.widgets.filter((w) => w.type === "combo");
widgetCombo[0].value = node.name;
widget.openpose = node.openPose.canvas.wrapperEl;
widget.parent = node;
// Background image
node.openPose.backgroundInput = $el("input", {
type: "file",
@@ -754,53 +743,27 @@ function createOpenPose(node, inputName, inputData, app) {
node.openPose.backgroundInput
);
document.body.appendChild(widget.openpose);
openPoseWrapper.appendChild(widget.openpose);
// Add buttons add, reset, undo, redo poses
node.addWidget("button", "Add pose", "add_pose", () => {
node.openPose.addPose();
});
node.onResize = function () {
let [w, h] = this.size;
node.addWidget("button", "Reset pose", "reset_pose", () => {
node.openPose.resetCanvas(true);
});
let aspect_ratio = 1;
// Add customWidget to node
node.addCustomWidget(widget);
aspect_ratio = node.openPose.canvas.height / node.openPose.canvas.width;
node.onRemoved = () => {
if (Object.hasOwn(LS_Poses, node.name)) {
delete LS_Poses[node.name];
LS_Save();
}
const buffer = 140;
// When removing this node we need to remove the input from the DOM
for (let y in node.widgets) {
if (node.widgets[y].openpose) {
node.widgets[y].openpose.remove();
}
}
if (w > 1024) w = w - (w - 1024);
if (w < 200) w = 200;
h = w * aspect_ratio + buffer;
if (h < 200) h = 200 + h / 2;
this.size = [w, h];
};
widget.onRemove = () => {
widget.openpose?.remove();
};
app.canvas.onDrawBackground = function () {
// Draw node isnt fired once the node is off the screen
// if it goes off screen quickly, the input may not be removed
// this shifts it off screen so it can be moved back if the node is visible.
for (let n in app.graph._nodes) {
const currNode = app.graph._nodes[n];
for (let w in currNode.widgets) {
let wid = currNode.widgets[w];
if (Object.hasOwn(wid, "openpose")) {
wid.openpose.style.left = -8000 + "px";
wid.openpose.style.position = "absolute";
}
}
}
};
return { widget: widget };
}
@@ -810,80 +773,87 @@ app.registerExtension({
async init(app) {
addStylesheet("css/posenode/pose_node_styles.css", import.meta.url);
},
async setup(app) {
let openPoseNode = app.graph._nodes.filter((wi) => wi.type == "PoseNode");
if (openPoseNode.length) {
openPoseNode.map((n) => {
console.log(`Setup PoseNode: ${n.name}`);
let widgetImage = n.widgets.find((w) => w.name == "image");
if (widgetImage && Object.hasOwn(LS_Poses, n.name)) {
let pose_ls = LS_Poses[n.name].undo_history;
n.openPose.loadPreset(
pose_ls.length > 0
? pose_ls[pose_ls.length - 1]
: { keypoints: default_keypoints }
);
}
});
}
},
async setup(app) {},
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "PoseNode") {
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
nodeType.prototype.onNodeCreated = async function () {
const r = onNodeCreated
? onNodeCreated.apply(this, arguments)
: undefined;
let openPoseNode = app.graph._nodes.filter(
(wi) => wi.type == "PoseNode"
),
nodeName = `Pose_${openPoseNode.length}`,
nodeNamePNG = `${nodeName}.png`;
const node_title = await this.getTitle();
const node_id = this.id;
const nodeName = `Pose_${node_id}`;
const nodeNamePNG = `${nodeName}.png`;
console.log(`Create PoseNode: ${nodeNamePNG}`);
LS_Poses =
localStorage.getItem("ComfyUI_Poses") &&
JSON.parse(localStorage.getItem("ComfyUI_Poses"));
if (!LS_Poses) {
localStorage.setItem("ComfyUI_Poses", JSON.stringify({}));
LS_Poses = JSON.parse(localStorage.getItem("ComfyUI_Poses"));
}
if (!Object.hasOwn(LS_Poses, nodeNamePNG)) {
LS_Poses[nodeNamePNG] = {
undo_history: [],
redo_history: [],
currentCanvasSize: { width: 512, height: 512 },
};
LS_Save();
}
createOpenPose.apply(this, [this, nodeNamePNG, {}, app]);
setTimeout(() => {
if (LS_Poses[nodeNamePNG]?.currentCanvasSize) {
this.openPose.currentCanvasSize =
LS_Poses[nodeNamePNG]?.currentCanvasSize;
this.openPose.setCanvasSize(
this.openPose.currentCanvasSize.width,
this.openPose.currentCanvasSize.height
);
}
this.openPose.uploadPoseFile(nodeNamePNG);
// Resize window
window.addEventListener("resize", (e) => resizeCanvas(this), false);
}, 1);
this.setSize([530, 620]);
this.openPose.uploadPoseFile(nodeNamePNG);
// Resize window
window.addEventListener("resize", (e) => resizeCanvas(this), false);
return r;
};
const onConfigure = nodeType.prototype.onConfigure;
nodeType.prototype.onConfigure = async function (widget) {
onConfigure?.apply(this, arguments);
setTimeout(() => {
const data = widget.widgets_values[3];
if (data) {
const {
currentCanvasSize,
background,
undo_history,
redo_history,
} = data;
if (currentCanvasSize) {
const { width, height } = currentCanvasSize;
this.openPose.setCanvasSize(width, height);
}
this.openPose.loadPreset(
undo_history.length
? undo_history[undo_history.length - 1]
: { keypoints: default_keypoints }
);
if (background) {
fabric.Image.fromURL(background, (img) => {
img.set({
originX: "left",
originY: "top",
opacity: 0.5,
});
this.openPose.canvas.setBackgroundImage(
img,
this.openPose.canvas.renderAll.bind(this.openPose.canvas),
{
scaleX: this.openPose.canvas.width / img.width,
scaleY: this.openPose.canvas.height / img.height,
}
);
});
}
this.openPose.settings = data;
this.openPose.value = data;
this.openPose.canvas.renderAll();
this.openPose.uploadPoseFile(this.name);
}
}, 0);
};
}
},
});
+1 -1
View File
@@ -285,7 +285,7 @@ def install_node(nodeElement):
# -- Copy tree folders
web_extensions_dir = os.path.join(extension_folder, extension_dirs[0])
extensions_dirs_copy = ["js", "css", "assets", "lib"]
extensions_dirs_copy = ["js", "css", "assets", "lib", "fonts"]
for dir_name in extensions_dirs_copy:
folder_curr = os.path.join(extension_folder, nodeElement, dir_name)
if os.path.exists(folder_curr):
+1 -1
View File
@@ -1,7 +1,7 @@
[project]
name = "comfyui_custom_nodes_alekpet"
description = "Nodes: PoseNode, PainterNode, TranslateTextNode, TranslateCLIPTextEncodeNode, DeepTranslatorTextNode, DeepTranslatorCLIPTextEncodeNode, ArgosTranslateTextNode, ArgosTranslateCLIPTextEncodeNode, ChatGLM4TranslateCLIPTextEncodeNode, ChatGLM4TranslateTextNode, ChatGLM4InstructNode, ChatGLM4InstructMediaNode, PreviewTextNode, HexToHueNode, ColorsCorrectNode, IDENode."
version = "1.0.37"
version = "1.0.45"
license = { file = "LICENSE" }
[project.urls]