1 Star 0 Fork 15

雪狐/ComfyUI_Lam_1

forked from yanlang0123/ComfyUI_Lam 
加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
文件
克隆/下载
openPoseEditorPlus.js 42.55 KB
一键复制 编辑 原始数据 按行查看 历史
马噥 提交于 1年前 . 新增节点 功能优化
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416
import { app } from "../../../scripts/app.js";
import { fabric } from "./fabric.js";
import { api } from "../../../scripts/api.js";
import {getDrawColor, hexToRgba} from "./utils.js"
import {PainterWidget,createMessage,loadData,LS_PSave} from "./painter_node.js"
const painters_settings_json = false;
fabric.Object.prototype.transparentCorners = false;
fabric.Object.prototype.cornerColor = "#108ce6";
fabric.Object.prototype.borderColor = "#108ce6";
fabric.Object.prototype.cornerSize = 10;
let connect_keypoints = [
[0, 1],
[1, 2],
[2, 3],
[3, 4],
[1, 5],
[5, 6],
[6, 7],
[1, 8],
[8, 9],
[9, 10],
[1, 11],
[11, 12],
[12, 13],
[0, 14],
[14, 16],
[0, 15],
[15, 17],
];
let connect_color = [
[0, 0, 255],
[255, 0, 0],
[255, 170, 0],
[255, 255, 0],
[255, 85, 0],
[170, 255, 0],
[85, 255, 0],
[0, 255, 0],
[0, 255, 85],
[0, 255, 170],
[0, 255, 255],
[0, 170, 255],
[0, 85, 255],
[85, 0, 255],
[170, 0, 255],
[255, 0, 255],
[255, 0, 170],
[255, 0, 85],
];
const default_keypoints = [
[241, 57],
[241, 120],
[191, 118],
[177, 183],
[163, 252],
[298, 118],
[317, 182],
[332, 245],
[225, 241],
[213, 359],
[215, 454],
[270, 240],
[282, 360],
[286, 456],
[230, 48],
[255, 48],
[215, 52],
[270, 52],
];
const default_subset=[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17]
const asyncLoadImg = (imgURL) => {
return new Promise((resolve,reject) => {
// 2.6.0 版本没有对图片加载异常的处理,需要自己处理
fabric.Image.fromURL(imgURL, img => {
// 加载出错提示,否则会造成渲染出错
if(img._originalElement === null){
reject('图片加载出错啦~~~')
}else {
resolve(img)
}
})
// 4.2.0版本有错误处理
// fabric.Image.fromURL(imgURL, (img,isError) => {})
})
}
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.groups = [];
this.subsets = [];
this.hands = [];
this.rects = [];
this.index = 1
this.history_index = 0;
this.history_change = false;
this.canvas = this.initCanvas(canvasElement);
this.disabled=false
// 创建用于选择图片的input元素
this.addPoseDemoInput = document.createElement("input");
this.addPoseDemoInput.type = "file";
this.addPoseDemoInput.accept = "image/*";
this.addPoseDemoInput.style.display = "none";
this.addPoseDemoInput.addEventListener("change", this.addPoseDemo.bind(this));
document.body.appendChild(this.addPoseDemoInput);
}
setCanvasWidth(width){
this.canvas.setWidth(width);
//this.painterCanvas.setWidth(width);
}
setCanvasHeight(height){
this.canvas.setHeight(height);
//this.painterCanvas.setHeight(height);
}
// 处理背景图片的加载
addPoseDemo(e) {
const file = this.addPoseDemoInput.files[0];
if(file){
this.getImageOpse(file)
}
}
// 设置背景图片
setBackgroundImage(url) {
fabric.Image.fromURL(url, (img) => {
img.set({
originX: 'left',
originY: 'top',
opacity: 0.5
});
this.setCanvasWidth(img.width)
this.setCanvasHeight(img.height)
this.canvas.setBackgroundImage(img, this.canvas.renderAll.bind(this.canvas));
});
}
//左右翻转
fliplf() {
let aobj=this.canvas.getActiveObject()
if(!aobj){
alert("请选择一只需要翻转的手")
return
}
if(aobj&&aobj['itype']&&aobj['itype']=='hand'){
this.canvas.getActiveObject().set('flipX', !this.canvas.getActiveObject().flipX);
this.canvas.renderAll();
}else{
alert("只有手能翻转")
}
}
//上下翻转
fliptd() {
let aobj=this.canvas.getActiveObject()
if(!aobj){
alert("请选择一只需要翻转的手")
return
}
if(aobj&&aobj['itype']&&aobj['itype']=='hand'){
this.canvas.getActiveObject().set('flipX', !this.canvas.getActiveObject().flipX);
this.canvas.renderAll();
}else{
alert("只有手能翻转")
}
}
addRect(){ //添加矩形遮罩
this.changeIndexPose()
this.groups.push({
left: 100, // 初始水平位置
top: 100, // 初始垂直位置
width: 100, // 正方形的宽度
height: 100, // 正方形的高度
fill: 'rgba(255,0,0,0.5)' // 填充颜色,这里设置为红色半透明
});
this.subsets.push({});
this.hands.push([]);
this.node.widgets[this.index].value = this.groups.length-1;
this.node.widgets[this.index].options['max']=this.groups.length-1;
for(let i=0;i<this.groups.length;i++){
if(!Array.isArray(this.groups[i])){
let indexColor=getDrawColor(i/this.groups.length,"7F") //hexToRgba(,0.5);
this.groups[i]['fill']=indexColor
}
}
this.setIndexPose(this.node.widgets[this.index].value,false);
}
// 异步加载图片
async addHand(obj,isEdit=true,group=undefined){
// 引入fabric.js库
let thi=this
let x=obj['l'],y=obj['t'];
let img = await asyncLoadImg(`/lam/getImage?type=hands&&name=${obj.name}`)
let left=x?x-img.width*0.1/2:0,top=y?y-img.height*0.1/2:0;
let nobj=Object.assign({
left: left, // 图片的初始水平位置
top: top, // 图片的初始垂直位置
angle: 0, // 图片的初始旋转角度
opacity: 0.95, // 图片透明度
// 这里可以通过scaleX和scaleY来设置图片绘制后的大小,这里为原来大小的一半
scaleX: 0.1,
scaleY: 0.1,
flipX:false,
flipY:false,
selectable: isEdit,
evented: isEdit,
}, obj);
img.set(nobj);
img['itype']='hand'
if(group){
group.addWithUpdate(img);
}else{
thi.canvas.add(img); // 将图片添加到画布上
}
if(x && y){
thi.canvas.setActiveObject(img); //选中
thi.hands[thi.history_index].push({
left: left, // 图片的初始水平位置
top: top, // 图片的初始垂直位置
angle: 0, // 图片的初始旋转角度
name:obj.name,
scaleX: 0.1,
scaleY: 0.1,
flipX:false,
flipY:false,
x:x,
y:y
})
thi.canvas.requestRenderAll();
}
return img
}
delIndexPose(){
let delIndex=this.history_index
this.groups.splice(delIndex,1)
this.subsets.splice(delIndex,1)
this.hands.splice(delIndex,1)
this.node.widgets[this.index].value =0;
this.node.widgets[this.index].options['max']=this.groups.length-1;
this.node.setDirtyCanvas(true);
this.setIndexPose(0,false)
}
setIndexPose(v,isChangIndex=true){
this.lockMode=true
if(isChangIndex){
this.changeIndexPose()
}
this.history_index=v
let groups=this.groups
let hands=this.hands
let vgroups=[]
for (let i = 0; i < groups.length; i++) {
if(i==v){
continue;
}
vgroups.push(groups[i])
}
this.setPose([],vgroups,false)
for (let i = 0; i < hands.length; i++) {
if(i==v){
continue;
}
for(let j = 0; j < hands[i].length; j++){
this.addHand(hands[i][j],false)
}
}
this.addPose(groups[v],true,hands[v])
this.lockMode=false
}
changeIndexPose(index=undefined){
if(index===undefined){
index=this.history_index
}
this.canvas.discardActiveObject();
let json=this.getJSON();
let keypoints=json["keypoints"]
if(Array.isArray(keypoints)){
if(keypoints.length>1){
let group=[]
let subset=this.subsets[index]
for(let i = 0; i < subset.length; i ++){
if(subset[i]<0){
group.push([-1,-1])
}else{
group.push(keypoints[subset[i]])
}
}
this.groups[index]=group
}
}else{
this.groups[index]=keypoints
}
if(json['hands'].length>0){
this.hands[index]=json['hands']
}else{
this.hands[index]=[]
}
}
setPose(keypoints,groups=[],isEdit=true) {
this.canvas.clear();
this.canvas.backgroundColor = "#000";
let res = [];
if(groups.length>0){
res=groups;
}
for (let i = 0; i < keypoints.length; i += 18) {
const chunk = keypoints.slice(i, i + 18);
res.push(chunk);
}
for (let item of res) {
this.addPose(item,isEdit);
this.canvas.discardActiveObject();
}
}
async addPose(keypoints = undefined,isEdit=true,hands=[]) {
if (keypoints === undefined) {
keypoints = default_keypoints;
this.changeIndexPose()
this.setPose([],this.groups,false)
this.groups.push(keypoints);
this.subsets.push(default_subset);
this.hands.push(hands);
this.node.widgets[this.index].value = this.groups.length-1;
this.node.widgets[this.index].options['max']=this.groups.length-1;
this.history_index=this.node.widgets[this.index].value
}
if(!Array.isArray(keypoints)){
let nobj=Object.assign({
left: 100, // 初始水平位置
top: 100, // 初始垂直位置
width: 100, // 正方形的宽度
height: 100, // 正方形的高度
lockRotation: true, // 设置不可旋转
selectable: isEdit,
evented: isEdit,
}, keypoints);
const square = new fabric.Rect(nobj);
this.canvas.add(square);
if(isEdit){
this.canvas.setActiveObject(square);
}
// 渲染画布
this.canvas.renderAll();
}else{
const group = new fabric.Group();
const makeCircle = (
color,
left,
top,
line1,
line2,
line3,
line4,
line5
) => {
let c = new fabric.Circle({
left: left,
top: top,
strokeWidth: 1,
radius: 5,
fill: color,
stroke: color,
});
c.hasControls = c.hasBorders = false;
c.line1 = line1;
c.line2 = line2;
c.line3 = line3;
c.line4 = line4;
c.line5 = line5;
return c;
};
const makeLine = (coords, color) => {
return new fabric.Line(coords, {
fill: color,
stroke: color,
strokeWidth: 10,
selectable: false,
evented: false,
});
};
const lines = {};
const circles = [];
for (let i = 0; i < connect_keypoints.length; i++) {
// 连线
const item = connect_keypoints[i];
if(keypoints[item[0]][0]<0||keypoints[item[0]][1]<0||keypoints[item[1]][0]<0||keypoints[item[1]][1]<0){
continue;
}
const line = makeLine(
keypoints[item[0]].concat(keypoints[item[1]]),
`rgba(${connect_color[i].join(", ")}, 0.7)`
);
lines[i]=line;
this.canvas.add(line);
}
for (let i = 0; i < keypoints.length; i++) {
if(keypoints[i][0]<0||keypoints[i][1]<0){
continue;
}
let list = [];
connect_keypoints.filter((item, idx) => {
if (item.includes(i)) {
list.push(lines[idx]);
return idx;
}
});
const circle = makeCircle(
`rgb(${connect_color[i].join(", ")})`,
keypoints[i][0],
keypoints[i][1],
...list
);
circle["id"] = i;
circles.push(circle);
group.addWithUpdate(circle);
}
for(let i = 0; i < hands.length; i++){
await this.addHand(hands[i],true,group)
}
this.canvas.discardActiveObject();
//不可编辑
if(isEdit){
this.canvas.setActiveObject(group);
this.canvas.add(group);
group.toActiveSelection();
}
this.canvas.requestRenderAll();
}
}
initCanvas() {
this.canvas = new fabric.Canvas(this.canvas, {
backgroundColor: "#000",
preserveObjectStacking: true,
});
const updateLines = (target) => {
if ("_objects" in target) {
const flipX = target.flipX ? -1 : 1;
const flipY = target.flipY ? -1 : 1;
this.flipped = flipX * flipY === -1;
const showEyes = this.flipped ? !this.visibleEyes : this.visibleEyes;
if (target.angle === 0) {
const rtop = target.top;
const rleft = target.left;
for (const item of target._objects) {
let p = item;
if((p['itype']&&p['itype']=='hand')||p.type=="rect"){
continue
}
p.scaleX = 1;
p.scaleY = 1;
const top =
rtop +
p.top * target.scaleY * flipY +
(target.height * target.scaleY) / 2;
const left =
rleft +
p.left * target.scaleX * flipX +
(target.width * target.scaleX) / 2;
p["_top"] = top;
p["_left"] = left;
if (p["id"] === 0) {
p.line1 && p.line1.set({ x1: left, y1: top });
} else {
p.line1 && p.line1.set({ x2: left, y2: top });
}
if (p["id"] === 14 || p["id"] === 15) {
p.radius = showEyes ? 5 : 0;
if (p.line1) p.line1.strokeWidth = showEyes ? 10 : 0;
if (p.line2) p.line2.strokeWidth = showEyes ? 10 : 0;
}
p.line2 && p.line2.set({ x1: left, y1: top });
p.line3 && p.line3.set({ x1: left, y1: top });
p.line4 && p.line4.set({ x1: left, y1: top });
p.line5 && p.line5.set({ x1: left, y1: top });
}
} else {
const aCoords = target.aCoords;
const center = {
x: (aCoords.tl.x + aCoords.br.x) / 2,
y: (aCoords.tl.y + aCoords.br.y) / 2,
};
const rad = (target.angle * Math.PI) / 180;
const sin = Math.sin(rad);
const cos = Math.cos(rad);
for (const item of target._objects) {
let p = item;
const p_top = p.top * target.scaleY * flipY;
const p_left = p.left * target.scaleX * flipX;
const left = center.x + p_left * cos - p_top * sin;
const top = center.y + p_left * sin + p_top * cos;
p["_top"] = top;
p["_left"] = left;
if (p["id"] === 0) {
p.line1 && p.line1.set({ x1: left, y1: top });
} else {
p.line1 && p.line1.set({ x2: left, y2: top });
}
if (p["id"] === 14 || p["id"] === 15) {
p.radius = showEyes ? 5 : 0.3;
if (p.line1) p.line1.strokeWidth = showEyes ? 10 : 0;
if (p.line2) p.line2.strokeWidth = showEyes ? 10 : 0;
}
p.line2 && p.line2.set({ x1: left, y1: top });
p.line3 && p.line3.set({ x1: left, y1: top });
p.line4 && p.line4.set({ x1: left, y1: top });
p.line5 && p.line5.set({ x1: left, y1: top });
}
}
} else {
var p = target;
if (p["id"] === 0) {
p.line1 && p.line1.set({ x1: p.left, y1: p.top });
} else {
p.line1 && p.line1.set({ x2: p.left, y2: p.top });
}
p.line2 && p.line2.set({ x1: p.left, y1: p.top });
p.line3 && p.line3.set({ x1: p.left, y1: p.top });
p.line4 && p.line4.set({ x1: p.left, y1: p.top });
p.line5 && p.line5.set({ x1: p.left, y1: p.top });
}
this.canvas.renderAll();
};
this.canvas.on("object:moving", (e) => {
updateLines(e.target);
});
this.canvas.on("object:scaling", (e) => {
updateLines(e.target);
this.canvas.renderAll();
});
this.canvas.on("object:rotating", (e) => {
updateLines(e.target);
this.canvas.renderAll();
});
this.canvas.on("object:modified", () => {
console.log('object:modified取消的事件')
if (
this.lockMode ||
(this.canvas.getActiveObject()&&this.canvas.getActiveObject().type == "activeSelection")
)
return;
this.changeIndexPose()
this.undo_history.push({'groups':JSON.parse(JSON.stringify(this.groups)),
'hands':JSON.parse(JSON.stringify(this.hands)),'index':this.history_index});
this.redo_history.length = 0;
this.history_change = true;
});
this.canvas.on("selection:cleared", (e) => {
if (this.lockMode)
return;
if(e?.deselected?.length>1||(e?.deselected?.length==1&&e?.deselected[0].itype=="hand")){
let json=this.getJSON();
if (!Array.isArray(json["keypoints"])||(json["keypoints"].length>0&&json["keypoints"].length >1)) {
this.changeIndexPose()
this.undo_history.push({'groups':JSON.parse(JSON.stringify(this.groups)),
'hands':JSON.parse(JSON.stringify(this.hands)),'index':this.history_index});
this.redo_history.length = 0;
this.history_change = true;
}
}
console.log('selection:cleared取消的事件')
});
if (!LS_Poses[this.node.name].undo_history.length) {
this.groups=[]
this.hands=[]
this.addPose();
this.undo_history.push({'groups':JSON.parse(JSON.stringify(this.groups)),
'hands':JSON.parse(JSON.stringify(this.hands)),
'index':this.history_index});
}
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());
const content = this.undo_history[this.undo_history.length - 1];
this.loadPreset(JSON.parse(JSON.stringify(content)));
this.canvas.renderAll();
this.lockMode = false;
this.history_change = true;
}
}
redo() {
if (this.redo_history.length > 0) {
this.lockMode = true;
const content = this.redo_history.pop();
this.undo_history.push(JSON.parse(JSON.stringify(content)));
this.loadPreset(content);
this.canvas.renderAll();
this.lockMode = false;
this.history_change = true;
}
}
resetCanvas() {
this.canvas.clear();
this.canvas.backgroundColor = "#000";
this.groups=[]
this.addPose();
this.undo_history.push({'groups':JSON.parse(JSON.stringify(this.groups)),
'hands':JSON.parse(JSON.stringify(this.hands)),
'index':this.history_index});
}
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;
}
}
getHands(){ //获取手势
let thi=this;
const addHandsBtn = async () => {
try {
const resp = await api.fetchApi(`/lam/getHeads`);
if (resp.status === 200) {
const data = await resp.json();
console.log(data)
let rightButtons = document.createElement("div");
rightButtons.className = "panelRightButtons comfy-menu-btns";
for(let i=0;i<data.length;i++){
var img = document.createElement('img');
img.src = `/lam/getImage?type=hands&&name=${data[i]}`;
img.alt = data[i];
img.style.width = "40px";
img.style.height = "40px";
img.addEventListener("dragend", (e) => {
let name=e.target.alt;
let x=e.x;
let y=e.y;
let width=thi.canvas.width;
let height=thi.canvas.height;
let oWidth=thi.canvas.upperCanvasEl.parentElement.offsetWidth
let oHeight=thi.canvas.upperCanvasEl.parentElement.offsetHeight
let left=thi.canvas.upperCanvasEl.parentElement.offsetLeft
let top=thi.canvas.upperCanvasEl.parentElement.offsetTop
x=(x-left)/oWidth*width
y=(y-top)/oHeight*height
thi.addHand({name:name,l:x,t:y})
});
rightButtons.appendChild(img);
}
thi.canvas.wrapperEl.appendChild(rightButtons);
} else {
alert(resp.status + " - " + resp.statusText);
}
} catch (error) {
console.error(error);
}
};
addHandsBtn();
}
getImageOpse(image){ //上传图片识别骨骼姿态
let thi=this;
// 创建一个自定义的Loading控件
var loading = (function() {
if(!thi.canvas.wrapperEl.loadingDiv){
var loadingDiv = document.createElement('div');
loadingDiv.style.position = 'absolute';
loadingDiv.style.left = '0';
loadingDiv.style.top = '0';
loadingDiv.style.right = '0';
loadingDiv.style.bottom = '0';
loadingDiv.style.background = 'rgba(255, 255, 255, 0.5)';
loadingDiv.style.display = 'flex';
loadingDiv.style.justifyContent = 'center';
loadingDiv.style.alignItems = 'center';
loadingDiv.style.zIndex = '100';
loadingDiv.style.textAlign = 'center';
loadingDiv.style.paddingTop = thi.canvas.height/2+'px';
var loadingText = document.createElement('p');
loadingText.textContent = '图片识别中...';
loadingDiv.appendChild(loadingText);
thi.canvas.wrapperEl.appendChild(loadingDiv);
thi.canvas.wrapperEl.loadingDiv = loadingDiv;
}
return {
show: function() {
thi.canvas.wrapperEl.loadingDiv.style.display = 'block';
},
hide: function() {
thi.canvas.wrapperEl.loadingDiv.style.display = 'none';
}
};
})();
// 在执行某些异步操作前显示Loading
loading.show();
const uploadFile = async (blobFile) => {
try {
const resp = await fetch("/lam/getImagePose", {
method: "POST",
body: blobFile,
});
if (resp.status === 200) {
const data = await resp.json();
console.log(data)
if(data.groups.length<=0){
alert('未识别到姿态骨骼');
// 操作完成后隐藏Loading
loading.hide();
return ;
}
for(let i = 0; i < data.groups.length; i++){
this.groups.push(data.groups[i]);
this.subsets.push(data.subsets[i]);
this.hands.push([]);
}
this.node.widgets[this.index].value = this.groups.length-1;
this.node.widgets[this.index].options['max']=this.groups.length-1;
this.node.setDirtyCanvas(true);
this.setIndexPose(this.groups.length-1,false)
this.updateHistoryData();
// 操作完成后隐藏Loading
loading.hide();
} else {
alert(resp.status + " - " + resp.statusText);
loading.hide();
}
} catch (error) {
console.error(error);
loading.hide();
}
};
let formData = new FormData();
formData.append("image", image, image.name);
uploadFile(formData);
}
getJSON() {
const json = {
keypoints: this.canvas
.getObjects()
.filter((item) => {
if (item.type === "circle" || (item.type === "rect" && item.selectable)) return item;
})
.map((item) => {
if(item.type === "rect"){
return {left:item.left,top:item.top,
width:item['scaleX']?item.width*item['scaleX']:item.width,
height:item['scaleY']?item.height*item['scaleY']:item.height,
fill:item.fill};
}
return [Math.round(item.left), Math.round(item.top)];
}),
hands:this.canvas
.getObjects()
.filter((item) => {
if (item.type === "image" && item.itype === "hand" && item.selectable ) return item;
})
.map((item) => {
let obj=item.getCenterPoint();
return Object.assign(obj,{left:item.left,top:item.top,
scaleX:item.scaleX,scaleY:item.scaleY,
flipX:item.flipX,flipY:item.flipY,
name:item.name,angle:item.angle
});
}),
};
if(json.keypoints.length==1&&!Array.isArray(json.keypoints[0])){
json.keypoints=json.keypoints[0];
}
console.log(json)
return json;
}
loadPreset(obj) {
this.groups=obj['groups']
let index=obj['index']
this.node.widgets[this.index].value=index;
this.node.widgets[this.index].options['max']=this.groups.length;
this.node.setDirtyCanvas(true);
this.setIndexPose(index,false)
}
}
// Create OpenPose widget
function createOpenPose(node, inputName, inputData, app) {
node.name = inputName;
const widget = {
type: "openpose",
name: `w${inputName}`,
value:'',
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 h=w/this.openpose.children[0].width*this.openpose.children[0].height
Object.assign(this.openpose.style, {
left: `${transform.a * margin + transform.e}px`,
top: `${transform.d + transform.f}px`,
width: w + "px",
height: h + "px",
position: "absolute",
zIndex: app.graph._nodes.indexOf(node),
});
Object.assign(this.openpose.children[0].style, {
width: w + "px",
height: h + "px",
});
Object.assign(this.openpose.children[1].style, {
width: w + "px",
height: h + "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`,
});
element.hidden = !visible;
});
},
};
// Fabric canvas
let canvasOpenPose = document.createElement("canvas");
let canvasPainter = document.createElement("canvas");
node.openPose = new OpenPose(node, canvasOpenPose,canvasPainter);
node.openPose.setCanvasWidth(512)
node.openPose.setCanvasHeight(512)
let index = node.widgets[node.widgets.findIndex(obj => obj.name === 'index')];
let width = node.widgets[node.widgets.findIndex(obj => obj.name === 'width')];
let height = node.widgets[node.widgets.findIndex(obj => obj.name === 'height')];
index.callback = function (v) {
node.openPose.setIndexPose(v)
}
width.callback = function (v) {
node.openPose.setCanvasWidth(v)
node.painter.setCanvasSize(v,node.painter.canvas.height)
}
height.callback = function (v) {
node.openPose.setCanvasHeight(v)
node.painter.setCanvasSize(node.painter.canvas.width,v)
}
widget.openpose = node.openPose.canvas.wrapperEl;
widget.parent = node;
Object.defineProperty(widget, "value", {
set: (x) => {
node.openPose.setCanvasWidth(width.value)
node.openPose.setCanvasHeight(height.value)
node.painter.setCanvasSize(width.value,height.value)
let data=JSON.parse(x)
node.openPose.groups=data.groups;
node.openPose.hands=data.hands;
node.openPose.subsets=data.subsets;
index.value =0;
index.options['max']=this.openPose.groups.length-1;
node.openPose.setIndexPose(0,false)
},
get: () => {
return JSON.stringify({
groups: this.openPose.groups,
subsets: this.openPose.subsets,
hands: this.openPose.hands,
});
}
});
// Create elements undo, redo, clear history
let panelButtons = document.createElement("div"),
addButton = document.createElement("button"),
delButton = document.createElement("button"),
resButton = document.createElement("button"),
refButton = document.createElement("button"),
undoButton = document.createElement("button"),
redoButton = document.createElement("button"),
fliplfButton = document.createElement("button"),
fliptdButton = document.createElement("button"),
rectButton = document.createElement("button"),
historyClearButton = document.createElement("button");
panelButtons.className = "panelButtons comfy-menu-btns";
addButton.textContent = "+";
delButton.textContent = "-";
resButton.textContent = "";
refButton.textContent = "img";
undoButton.textContent = "<-";
redoButton.textContent = "->";
fliplfButton.textContent = "";
fliptdButton.textContent = "";
rectButton.textContent = "";
historyClearButton.textContent = "";
addButton.title = "添加骨骼";
delButton.title = "删除骨骼";
resButton.title = "重置";
refButton.title = "添加人物示例";
undoButton.title = "上一步";
redoButton.title = "下一步";
fliplfButton.title = "左右翻转";
fliptdButton.title = "上下翻转";
rectButton.title="矩形遮罩";
historyClearButton.title = "清除历史";
addButton.addEventListener("click", () => node.openPose.addPose());
delButton.addEventListener("click", () => node.openPose.delIndexPose());
resButton.addEventListener("click", () => node.openPose.resetCanvas());
refButton.addEventListener("click", () => node.openPose.addPoseDemoInput.click());
undoButton.addEventListener("click", () => node.openPose.undo());
redoButton.addEventListener("click", () => node.openPose.redo());
fliplfButton.addEventListener("click", () => node.openPose.fliplf());
fliptdButton.addEventListener("click", () => node.openPose.fliptd());
rectButton.addEventListener("click", () => node.openPose.addRect());
historyClearButton.addEventListener("click", () => {
if (confirm(`删除节点"${node.name}"的所有姿势历史记录 ?`)) {
node.openPose.undo_history = [];
node.openPose.redo_history = [];
if(node.openPose.groups.length>0){
node.openPose.undo_history.push({'groups':JSON.parse(JSON.stringify(node.openPose.groups)),
'hands':JSON.parse(JSON.stringify(node.openPose.hands)),
'index':node.openPose.history_index});
}
node.openPose.groups=[]
node.openPose.addPose();
node.openPose.history_change = true;
node.openPose.updateHistoryData();
}
});
panelButtons.appendChild(addButton);
panelButtons.appendChild(delButton);
panelButtons.appendChild(resButton);
panelButtons.appendChild(refButton);
panelButtons.appendChild(undoButton);
panelButtons.appendChild(redoButton);
panelButtons.appendChild(fliplfButton);
panelButtons.appendChild(fliptdButton);
panelButtons.appendChild(rectButton);
panelButtons.appendChild(historyClearButton);
node.openPose.canvas.wrapperEl.appendChild(panelButtons);
document.body.appendChild(widget.openpose);
document.addEventListener('keydown', function(event) {
if(node.openPose.disabled){
return;
}
if(event.key=='Delete'||event.key=='Backspace'){
let activeObject=node.openPose.canvas.getActiveObject()
if (activeObject) {
node.openPose.canvas.remove(activeObject);
}
node.openPose.setIndexPose(node.openPose.history_index,true)
}
if(event.key=='ArrowLeft'){
node.widgets[node.openPose.index].value=node.widgets[node.openPose.index].value!=0?node.widgets[node.openPose.index].value-1:node.widgets[node.openPose.index].options['max']
node.openPose.setIndexPose(node.widgets[node.openPose.index].value)
}
if(event.key=='ArrowRight'){
node.widgets[node.openPose.index].value=node.widgets[node.openPose.index].value!=node.widgets[node.openPose.index].options['max']?node.widgets[node.openPose.index].value+1:0
node.openPose.setIndexPose(node.widgets[node.openPose.index].value)
}
});
node.openPose.getHands()
node.addWidget("button", "显/隐画板", "ShowPainter", () => {
node.painter.disabled=!node.painter.disabled
node.openPose.disabled=!node.openPose.disabled
if(node.openPose.disabled){
panelButtons.style.display="none"
node.openPose.canvas.wrapperEl.querySelectorAll('.panelRightButtons')[0].style.display="none"
}else{
panelButtons.style.display="flex"
node.openPose.canvas.wrapperEl.querySelectorAll('.panelRightButtons')[0].style.display="flex"
}
});
// Add customWidget to node
node.addCustomWidget(widget);
node.onRemoved = () => {
if (Object.hasOwn(LS_Poses, node.name)) {
delete LS_Poses[node.name];
LS_Save();
}
// 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 (node.widgets[y].painter_wrap) {
node.widgets[y].painter_wrap.remove();
}
}
};
widget.onRemove = () => {
widget.openpose?.remove();
widget.painter_wrap?.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) {
n = graph._nodes[n];
for (let w in n.widgets) {
let wid = n.widgets[w];
if (Object.hasOwn(wid, "openpose")) {
wid.openpose.style.left = -8000 + "px";
wid.openpose.style.position = "absolute";
}
}
}
};
return { widget: widget };
}
window.LS_Poses = {};
function LS_Save() {
///console.log("Save:", LS_Poses);
localStorage.setItem("ComfyUI_Poses", JSON.stringify(LS_Poses));
}
app.registerExtension({
name: "LAM.OpenPoseEditorPlus",
async init(app) {
// Any initial setup to run as soon as the page loads
let style = document.createElement("style");
style.innerText = `
.panelButtons{
position: absolute;
padding: 4px;
display: flex;
gap: 4px;
flex-direction: column;
width: fit-content;
}
.panelButtons button:last-child{
border-color: var(--error-text);
color: var(--error-text) !important;
}
.panelRightButtons{
right: 0px;
overflow: auto;
height: 95%;
position: absolute;
padding: 4px;
display: flex;
gap: 4px;
flex-direction: column;
width: fit-content;
}
.panelRightButtons button:last-child{
border-color: var(--error-text);
color: var(--error-text) !important;
}
.panelPaintBox {
position: absolute;
width: 100%;
}
.comfy-menu-btns button.active {
color: var(--error-text) !important;
font-weight: bold;
border: 1px solid;
}
.painter_manipulation_box {
position: absolute;
left: 50%;
top: -40px;
transform: translateX(-50%);
}
.painter_manipulation_box > div {
display: grid;
gap: 2px;
grid-template-columns: 0.1fr 0.1fr 0.1fr 0.1fr 0.1fr;
justify-content: center;
margin-bottom: 2px;
}
.painter_manipulation_box > div button {
font-size: 0.5rem;
}
.painter_manipulation_box > div button[id^=zpos]:active {
background-color: #484848;
}
.painter_drawning_box {
position: absolute;
top: -40px;
width: 88px;
}
.painter_drawning_box button {
width: 24px;
}
.painter_drawning_box_property {
position: absolute;
top: -36px;
}
.painter_drawning_box_property select {
color: var(--input-text);
background-color: var(--comfy-input-bg);
border-radius: 4px;
border-color: var(--border-color);
border-style: solid;
}
.separator {
width: 1px;
height: 25px;
background-color: var(--border-color);
display: inline-block;
vertical-align: middle;
margin: 0 4px 0 4px;
}
.painter_drawning_box > div {
display: flex;
flex-direction: column;
gap: 2px;
align-items: center;
}
.painter_drawning_box input[type="number"] {
width: 2.6rem;
color: var(--input-text);
background-color: var(--comfy-input-bg);
border-radius: 8px;
border-color: var(--border-color);
border-style: solid;
font-size: inherit;
}
.painter_colors_box input[type="color"], .painter_bg_setting input[type="color"] {
width: 1.5rem;
height: 1.5rem;
padding: 0;
margin-bottom: 5px;
color: var(--input-text);
background-color: var(--comfy-input-bg);
border-radius: 8px;
border-color: var(--border-color);
border-style: solid;
font-size: inherit;
}
.painter_bg_setting button{
width: 40px;
height: 20px;
}
.painter_bg_setting #bgColor:after {
content: attr(data-label);
position: absolute;
color: var(--input-text);
left: 70%;
font-size: 0.5rem;
margin-top: -18%;
}
.painter_colors_box input[type="color"]::-webkit-color-swatch-wrapper, .painter_bg_setting input[type="color"]::-webkit-color-swatch-wrapper {
padding: 1px !important;
}
.painter_grid_style {
display: grid;
gap: 3px;
font-size: 0.55rem;
text-align: center;
align-items: start;
justify-items: center;
}
.painter_shapes_box {
grid-template-columns: 1fr 1fr 1fr;
}
.painter_colors_alpha {
grid-template-columns: 0.7fr 0.7fr;
}
.fieldset_box {
padding: 2px;
margin: 15px 0 2px 0;
position: relative;
text-align: center;
}
.fieldset_box:before {
content: attr(f_name) ":";
font-size: 0.6rem;
position: absolute;
top: -10px;
color: yellow;
left: 0;
right: 0;
}
.list_objects_panel {
width: 90%;
font-size: 0.7rem;
}
.list_objects_panel__items {
display: flex;
flex-direction: column;
align-items: center;
overflow-y: auto;
max-height: 350px;
}
.list_objects_panel__items button {
width: 80% !important;
}
.list_objects_panel__items::-webkit-scrollbar {
width: 8px;
}
.list_objects_panel__items::-webkit-scrollbar-track {
background-color: var(--descrip-text);
}
.list_objects_panel__items::-webkit-scrollbar-thumb {
background-color: var(--fg-color);
}
.list_objects_align {
display: flex;
flex-direction: column;
text-align: center;
justify-content: space-between;
height: 430px;
}
.list_objects_align > div{
flex: 1;
}
.painter_history_panel {
position: absolute;
padding: 4px;
display: flex;
gap: 4px;
right: 0;
opacity: 0.8;
flex-direction: row;
width: fit-content;
}
.painter_history_panel > button {
background: transparent;
}
.painter_history_panel > button:hover:enabled {
opacity: 1;
border-color: var(--error-text);
color: var(--error-text) !important;
}
.painter_history_panel > button:disabled {
opacity: .5;
}
.painter_stroke_box {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
}
.painter_stroke_box > label span {
margin-right: 9px;
vertical-align: middle;
font-size: 10px;
color: var(--input-text);
}
.property_brushesBox, .property_brushesSecondBox {
display: flex;
gap: 5px;
}
.property_brushesSecondBox > button {
min-width: 30px;
font-size: 0.5rem;
}
.property_brushesSecondBox svg {
width: 100%;
height: 100%;
}
.active svg > .cls-2 {
fill: var(--error-text);
}
.active svg > * {
stroke: var(--error-text);
}
`;
document.head.appendChild(style);
},
async setup(app) {
let openPoseNode = app.graph._nodes.filter((wi) => wi.type == "LAM.OpenPoseEditorPlus");
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;
if(pose_ls.length>0){
n.openPose.loadPreset(pose_ls[pose_ls.length - 1]);
}else{
n.openPose.groups=[]
n.openPose.addPose();
n.openPose.undo_history.push({'groups':JSON.parse(JSON.stringify(n.openPose.groups)),
'hands':JSON.parse(JSON.stringify(n.openPose.hands)),
'index':n.openPose.history_index});
}
}
});
}
},
async beforeRegisterNodeDef(nodeType, nodeData, app) {
if (nodeData.name === "LAM.OpenPoseEditorPlus") {
if (painters_settings_json) {
const message = createMessage(
"Loading",
"Please wait, <span style='font-weight: bold; color: orange'>Painter node</span> settings are loading. Loading times may take a long time if large images have been added to the canvas!"
);
LS_Painters = await loadData();
document.body.removeChild(message);
} else {
LS_Painters =
localStorage.getItem("ComfyUI_Painter") &&
JSON.parse(localStorage.getItem("ComfyUI_Painter"));
if (!LS_Painters || LS_Painters === undefined) {
localStorage.setItem("ComfyUI_Painter", JSON.stringify({}));
LS_Painters = JSON.parse(localStorage.getItem("ComfyUI_Painter"));
}
}
const onNodeCreated = nodeType.prototype.onNodeCreated;
nodeType.prototype.onNodeCreated = function () {
const r = onNodeCreated
? onNodeCreated.apply(this, arguments)
: undefined;
let openPoseNode = app.graph._nodes.filter((wi) => wi.type == "LAM.OpenPoseEditorPlus");
let nodeName = `Pose_${openPoseNode.length}`;
let nodeNamePNG = `${nodeName}`;
console.log(`Create PoseNode: ${nodeName}`);
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: [],
};
LS_Save();
}
if (LS_Painters && !Object.hasOwn(LS_Painters, nodeNamePNG)) {
LS_Painters[nodeNamePNG] = {
undo_history: [],
redo_history: [],
canvas_settings: { background: "#000000" },
settings: {},
};
LS_PSave();
}
createOpenPose.apply(this, [this, nodeNamePNG, {}, app]);
PainterWidget.apply(this, [this, nodeNamePNG, {}, app]);
this.setSize([750, 950]);
return r;
};
}
},
});
Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/assassindesign/ComfyUI_Lam_1.git
git@gitee.com:assassindesign/ComfyUI_Lam_1.git
assassindesign
ComfyUI_Lam_1
ComfyUI_Lam_1
cu121

搜索帮助