diff --git a/ExtrasNode/js/extras_node.js b/ExtrasNode/js/extras_node.js
index e28dc57..e1d6fa7 100644
--- a/ExtrasNode/js/extras_node.js
+++ b/ExtrasNode/js/extras_node.js
@@ -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`,
diff --git a/ExtrasNode/js/utils.js b/ExtrasNode/js/utils.js
index 3c0d92b..1db8a7b 100644
--- a/ExtrasNode/js/utils.js
+++ b/ExtrasNode/js/utils.js
@@ -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;
}
diff --git a/PainterNode/README.md b/PainterNode/README.md
index cd8a64a..9b2d168 100644
--- a/PainterNode/README.md
+++ b/PainterNode/README.md
@@ -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))

+
+# 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");
+```
diff --git a/PainterNode/css/painter_node_fonts.css b/PainterNode/css/painter_node_fonts.css
new file mode 100644
index 0000000..f9b6b74
--- /dev/null
+++ b/PainterNode/css/painter_node_fonts.css
@@ -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");
diff --git a/PainterNode/css/painter_node_user_fonts.css b/PainterNode/css/painter_node_user_fonts.css
new file mode 100644
index 0000000..0621b0b
--- /dev/null
+++ b/PainterNode/css/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");
+}
diff --git a/PainterNode/fonts/HarryFontYourssincerelylarry.ttf b/PainterNode/fonts/HarryFontYourssincerelylarry.ttf
new file mode 100644
index 0000000..e57cd7d
Binary files /dev/null and b/PainterNode/fonts/HarryFontYourssincerelylarry.ttf differ
diff --git a/PainterNode/fonts/pricedown.otf b/PainterNode/fonts/pricedown.otf
new file mode 100644
index 0000000..f06a2be
Binary files /dev/null and b/PainterNode/fonts/pricedown.otf differ
diff --git a/PainterNode/fonts/put files fonts here b/PainterNode/fonts/put files fonts here
new file mode 100644
index 0000000..e69de29
diff --git a/PainterNode/js/painter_node.js b/PainterNode/js/painter_node.js
index 835b987..37a81e5 100644
--- a/PainterNode/js/painter_node.js
+++ b/PainterNode/js/painter_node.js
@@ -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:
+ "If fonts not loaded in the canvas, refresh page browser!😅",
+ ...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)"
diff --git a/PainterNode/lib/fontfaceobserver.js b/PainterNode/lib/fontfaceobserver.js
new file mode 100644
index 0000000..3177ec6
--- /dev/null
+++ b/PainterNode/lib/fontfaceobserver.js
@@ -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);}());
diff --git a/PoseNode/css/pose_node_styles.css b/PoseNode/css/pose_node_styles.css
index 2cb5f77..e344ecd 100644
--- a/PoseNode/css/pose_node_styles.css
+++ b/PoseNode/css/pose_node_styles.css
@@ -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;
diff --git a/PoseNode/js/pose_node.js b/PoseNode/js/pose_node.js
index f89371c..a164b77 100644
--- a/PoseNode/js/pose_node.js
+++ b/PoseNode/js/pose_node.js
@@ -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: `