mirror of
https://github.com/Azornes/Comfyui-LayerForge.git
synced 2026-03-24 22:12:17 -03:00
Add files via upload
This commit is contained in:
434
js/Canvas_view.js
Normal file
434
js/Canvas_view.js
Normal file
@@ -0,0 +1,434 @@
|
||||
import { app } from "../../scripts/app.js";
|
||||
import { api } from "../../scripts/api.js";
|
||||
import { $el } from "../../scripts/ui.js";
|
||||
import { Canvas } from "./Canvas.js";
|
||||
|
||||
async function createCanvasWidget(node, widget, app) {
|
||||
const canvas = new Canvas(node, widget);
|
||||
|
||||
// 添加全局样式
|
||||
const style = document.createElement('style');
|
||||
style.textContent = `
|
||||
.painter-button {
|
||||
background: linear-gradient(to bottom, #4a4a4a, #3a3a3a);
|
||||
border: 1px solid #2a2a2a;
|
||||
border-radius: 4px;
|
||||
color: #ffffff;
|
||||
padding: 6px 12px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
min-width: 80px;
|
||||
text-align: center;
|
||||
margin: 2px;
|
||||
text-shadow: 0 1px 1px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.painter-button:hover {
|
||||
background: linear-gradient(to bottom, #5a5a5a, #4a4a4a);
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
|
||||
}
|
||||
|
||||
.painter-button:active {
|
||||
background: linear-gradient(to bottom, #3a3a3a, #4a4a4a);
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.painter-button.primary {
|
||||
background: linear-gradient(to bottom, #4a6cd4, #3a5cc4);
|
||||
border-color: #2a4cb4;
|
||||
}
|
||||
|
||||
.painter-button.primary:hover {
|
||||
background: linear-gradient(to bottom, #5a7ce4, #4a6cd4);
|
||||
}
|
||||
|
||||
.painter-controls {
|
||||
background: linear-gradient(to bottom, #404040, #383838);
|
||||
border-bottom: 1px solid #2a2a2a;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
padding: 8px;
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.painter-container {
|
||||
background: #607080; /* 带蓝色的灰色背景 */
|
||||
border: 1px solid #4a5a6a;
|
||||
border-radius: 6px;
|
||||
box-shadow: inset 0 0 10px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.painter-dialog {
|
||||
background: #404040;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
|
||||
padding: 20px;
|
||||
color: #ffffff;
|
||||
}
|
||||
|
||||
.painter-dialog input {
|
||||
background: #303030;
|
||||
border: 1px solid #505050;
|
||||
border-radius: 4px;
|
||||
color: #ffffff;
|
||||
padding: 4px 8px;
|
||||
margin: 4px;
|
||||
width: 80px;
|
||||
}
|
||||
|
||||
.painter-dialog button {
|
||||
background: #505050;
|
||||
border: 1px solid #606060;
|
||||
border-radius: 4px;
|
||||
color: #ffffff;
|
||||
padding: 4px 12px;
|
||||
margin: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.painter-dialog button:hover {
|
||||
background: #606060;
|
||||
}
|
||||
`;
|
||||
document.head.appendChild(style);
|
||||
|
||||
// 修改控制面板,使其高度自适应
|
||||
const controlPanel = $el("div.painterControlPanel", {}, [
|
||||
$el("div.controls.painter-controls", {
|
||||
style: {
|
||||
position: "absolute",
|
||||
top: "0",
|
||||
left: "0",
|
||||
right: "0",
|
||||
minHeight: "50px", // 改为最小高度
|
||||
zIndex: "10",
|
||||
background: "linear-gradient(to bottom, #404040, #383838)",
|
||||
borderBottom: "1px solid #2a2a2a",
|
||||
boxShadow: "0 2px 4px rgba(0,0,0,0.1)",
|
||||
padding: "8px",
|
||||
display: "flex",
|
||||
gap: "6px",
|
||||
flexWrap: "wrap",
|
||||
alignItems: "center"
|
||||
},
|
||||
// 添加监听器来动态调整画布容器的位置
|
||||
onresize: (entries) => {
|
||||
const controlsHeight = entries[0].target.offsetHeight;
|
||||
canvasContainer.style.top = (controlsHeight + 10) + "px";
|
||||
}
|
||||
}, [
|
||||
$el("button.painter-button.primary", {
|
||||
textContent: "Add Image",
|
||||
onclick: () => {
|
||||
const input = document.createElement('input');
|
||||
input.type = 'file';
|
||||
input.accept = 'image/*';
|
||||
input.multiple = true;
|
||||
input.onchange = async (e) => {
|
||||
for (const file of e.target.files) {
|
||||
// 创建图片对象
|
||||
const img = new Image();
|
||||
img.onload = async () => {
|
||||
// 计算适当的缩放比例
|
||||
const scale = Math.min(
|
||||
canvas.width / img.width * 0.8,
|
||||
canvas.height / img.height * 0.8
|
||||
);
|
||||
|
||||
// 创建新图层
|
||||
const layer = {
|
||||
image: img,
|
||||
x: (canvas.width - img.width * scale) / 2,
|
||||
y: (canvas.height - img.height * scale) / 2,
|
||||
width: img.width * scale,
|
||||
height: img.height * scale,
|
||||
rotation: 0,
|
||||
zIndex: canvas.layers.length
|
||||
};
|
||||
|
||||
// 添加图层并选中
|
||||
canvas.layers.push(layer);
|
||||
canvas.selectedLayer = layer;
|
||||
|
||||
// 渲染画布
|
||||
canvas.render();
|
||||
|
||||
// 立即保存并触发输出更新
|
||||
await canvas.saveToServer(widget.value);
|
||||
|
||||
// 触发节点更新
|
||||
app.graph.runStep();
|
||||
};
|
||||
img.src = URL.createObjectURL(file);
|
||||
}
|
||||
};
|
||||
input.click();
|
||||
}
|
||||
}),
|
||||
$el("button.painter-button", {
|
||||
textContent: "Canvas Size",
|
||||
onclick: () => {
|
||||
const dialog = $el("div.painter-dialog", {
|
||||
style: {
|
||||
position: 'fixed',
|
||||
left: '50%',
|
||||
top: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
zIndex: '1000'
|
||||
}
|
||||
}, [
|
||||
$el("div", {
|
||||
style: {
|
||||
color: "white",
|
||||
marginBottom: "10px"
|
||||
}
|
||||
}, [
|
||||
$el("label", {
|
||||
style: {
|
||||
marginRight: "5px"
|
||||
}
|
||||
}, [
|
||||
$el("span", {}, ["Width: "])
|
||||
]),
|
||||
$el("input", {
|
||||
type: "number",
|
||||
id: "canvas-width",
|
||||
value: canvas.width,
|
||||
min: "1",
|
||||
max: "4096"
|
||||
})
|
||||
]),
|
||||
$el("div", {
|
||||
style: {
|
||||
color: "white",
|
||||
marginBottom: "10px"
|
||||
}
|
||||
}, [
|
||||
$el("label", {
|
||||
style: {
|
||||
marginRight: "5px"
|
||||
}
|
||||
}, [
|
||||
$el("span", {}, ["Height: "])
|
||||
]),
|
||||
$el("input", {
|
||||
type: "number",
|
||||
id: "canvas-height",
|
||||
value: canvas.height,
|
||||
min: "1",
|
||||
max: "4096"
|
||||
})
|
||||
]),
|
||||
$el("div", {
|
||||
style: {
|
||||
textAlign: "right"
|
||||
}
|
||||
}, [
|
||||
$el("button", {
|
||||
id: "cancel-size",
|
||||
textContent: "Cancel"
|
||||
}),
|
||||
$el("button", {
|
||||
id: "confirm-size",
|
||||
textContent: "OK"
|
||||
})
|
||||
])
|
||||
]);
|
||||
document.body.appendChild(dialog);
|
||||
|
||||
document.getElementById('confirm-size').onclick = () => {
|
||||
const width = parseInt(document.getElementById('canvas-width').value) || canvas.width;
|
||||
const height = parseInt(document.getElementById('canvas-height').value) || canvas.height;
|
||||
canvas.updateCanvasSize(width, height);
|
||||
document.body.removeChild(dialog);
|
||||
};
|
||||
|
||||
document.getElementById('cancel-size').onclick = () => {
|
||||
document.body.removeChild(dialog);
|
||||
};
|
||||
}
|
||||
}),
|
||||
$el("button.painter-button", {
|
||||
textContent: "Remove Layer",
|
||||
onclick: () => {
|
||||
const index = canvas.layers.indexOf(canvas.selectedLayer);
|
||||
canvas.removeLayer(index);
|
||||
}
|
||||
}),
|
||||
$el("button.painter-button", {
|
||||
textContent: "Rotate +90°",
|
||||
onclick: () => canvas.rotateLayer(90)
|
||||
}),
|
||||
$el("button.painter-button", {
|
||||
textContent: "Scale +5%",
|
||||
onclick: () => canvas.resizeLayer(1.05)
|
||||
}),
|
||||
$el("button.painter-button", {
|
||||
textContent: "Scale -5%",
|
||||
onclick: () => canvas.resizeLayer(0.95)
|
||||
}),
|
||||
$el("button.painter-button", {
|
||||
textContent: "Layer Up",
|
||||
onclick: async () => {
|
||||
canvas.moveLayerUp();
|
||||
await canvas.saveToServer(widget.value);
|
||||
app.graph.runStep();
|
||||
}
|
||||
}),
|
||||
$el("button.painter-button", {
|
||||
textContent: "Layer Down",
|
||||
onclick: async () => {
|
||||
canvas.moveLayerDown();
|
||||
await canvas.saveToServer(widget.value);
|
||||
app.graph.runStep();
|
||||
}
|
||||
}),
|
||||
// 添加水平镜像按钮
|
||||
$el("button.painter-button", {
|
||||
textContent: "Mirror H",
|
||||
onclick: () => {
|
||||
canvas.mirrorHorizontal();
|
||||
}
|
||||
}),
|
||||
// 添加垂直镜像按钮
|
||||
$el("button.painter-button", {
|
||||
textContent: "Mirror V",
|
||||
onclick: () => {
|
||||
canvas.mirrorVertical();
|
||||
}
|
||||
})
|
||||
])
|
||||
]);
|
||||
|
||||
// 创建ResizeObserver来监控控制面板的高度变化
|
||||
const resizeObserver = new ResizeObserver((entries) => {
|
||||
const controlsHeight = entries[0].target.offsetHeight;
|
||||
canvasContainer.style.top = (controlsHeight + 10) + "px";
|
||||
});
|
||||
|
||||
// 监控控制面板的大小变化
|
||||
resizeObserver.observe(controlPanel.querySelector('.controls'));
|
||||
|
||||
// 获取触发器widget
|
||||
const triggerWidget = node.widgets.find(w => w.name === "trigger");
|
||||
|
||||
// 创建更新函数
|
||||
const updateOutput = async () => {
|
||||
// 保存画布
|
||||
await canvas.saveToServer(widget.value);
|
||||
// 更新触发器值
|
||||
triggerWidget.value = (triggerWidget.value + 1) % 99999999;
|
||||
// 触发节点更新
|
||||
app.graph.runStep();
|
||||
};
|
||||
|
||||
// 修改所有可能触发更新的操作
|
||||
const addUpdateToButton = (button) => {
|
||||
const origClick = button.onclick;
|
||||
button.onclick = async (...args) => {
|
||||
await origClick?.(...args);
|
||||
await updateOutput();
|
||||
};
|
||||
};
|
||||
|
||||
// 为所有按钮添加更新逻辑
|
||||
controlPanel.querySelectorAll('button').forEach(addUpdateToButton);
|
||||
|
||||
// 修改画布容器样式,使用动态top值
|
||||
const canvasContainer = $el("div.painterCanvasContainer.painter-container", {
|
||||
style: {
|
||||
position: "absolute",
|
||||
top: "60px", // 初始值
|
||||
left: "10px",
|
||||
right: "10px",
|
||||
bottom: "10px",
|
||||
display: "flex",
|
||||
justifyContent: "center",
|
||||
alignItems: "center",
|
||||
overflow: "hidden"
|
||||
}
|
||||
}, [canvas.canvas]);
|
||||
|
||||
// 修改节点大小调整逻辑
|
||||
node.onResize = function() {
|
||||
const minSize = 300;
|
||||
const controlsElement = controlPanel.querySelector('.controls');
|
||||
const controlPanelHeight = controlsElement.offsetHeight; // 获取实际高度
|
||||
const padding = 20;
|
||||
|
||||
// 保持节点宽度,高度根据画布比例调整
|
||||
const width = Math.max(this.size[0], minSize);
|
||||
const height = Math.max(
|
||||
width * (canvas.height / canvas.width) + controlPanelHeight + padding * 2,
|
||||
minSize + controlPanelHeight
|
||||
);
|
||||
|
||||
this.size[0] = width;
|
||||
this.size[1] = height;
|
||||
|
||||
// 计算画布的实际可用空间
|
||||
const availableWidth = width - padding * 2;
|
||||
const availableHeight = height - controlPanelHeight - padding * 2;
|
||||
|
||||
// 更新画布尺寸,保持比例
|
||||
const scale = Math.min(
|
||||
availableWidth / canvas.width,
|
||||
availableHeight / canvas.height
|
||||
);
|
||||
|
||||
canvas.canvas.style.width = (canvas.width * scale) + "px";
|
||||
canvas.canvas.style.height = (canvas.height * scale) + "px";
|
||||
|
||||
// 强制重新渲染
|
||||
canvas.render();
|
||||
};
|
||||
|
||||
// 添加拖拽事件监听
|
||||
canvas.canvas.addEventListener('mouseup', updateOutput);
|
||||
canvas.canvas.addEventListener('mouseleave', updateOutput);
|
||||
|
||||
// 创建一个包含控制面板和画布的容器
|
||||
const mainContainer = $el("div.painterMainContainer", {
|
||||
style: {
|
||||
position: "relative",
|
||||
width: "100%",
|
||||
height: "100%"
|
||||
}
|
||||
}, [controlPanel, canvasContainer]);
|
||||
|
||||
// 将主容器添加到节点
|
||||
const mainWidget = node.addDOMWidget("mainContainer", "widget", mainContainer);
|
||||
|
||||
// 设置节点的默认大小
|
||||
node.size = [500, 500]; // 设置初始大小为正方形
|
||||
|
||||
// 在执行时保存画布
|
||||
api.addEventListener("execution_start", async () => {
|
||||
await canvas.saveToServer(widget.value);
|
||||
});
|
||||
|
||||
return {
|
||||
canvas: canvas,
|
||||
panel: controlPanel
|
||||
};
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: "Comfy.CanvasView",
|
||||
async beforeRegisterNodeDef(nodeType, nodeData, app) {
|
||||
if (nodeType.comfyClass === "CanvasView") {
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated;
|
||||
nodeType.prototype.onNodeCreated = async function() {
|
||||
const r = onNodeCreated?.apply(this, arguments);
|
||||
|
||||
const widget = this.widgets.find(w => w.name === "canvas_image");
|
||||
await createCanvasWidget(this, widget, app);
|
||||
|
||||
return r;
|
||||
};
|
||||
}
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user