|
import { app } from '../../../scripts/app.js' |
|
import { api } from '../../../scripts/api.js' |
|
import { ComfyWidgets } from '../../../scripts/widgets.js' |
|
import { $el } from '../../../scripts/ui.js' |
|
|
|
function downloadJsonFile (jsonData, fileName = 'grid.json') { |
|
const dataString = JSON.stringify(jsonData) |
|
const blob = new Blob([dataString], { type: 'application/json' }) |
|
const url = URL.createObjectURL(blob) |
|
|
|
const link = document.createElement('a') |
|
link.href = url |
|
link.download = fileName |
|
link.click() |
|
|
|
|
|
setTimeout(() => { |
|
URL.revokeObjectURL(url) |
|
}, 0) |
|
} |
|
|
|
function createSelectWithOptions (options) { |
|
const select = document.createElement('select') |
|
|
|
options.forEach(option => { |
|
const optionElement = document.createElement('option') |
|
optionElement.text = option |
|
optionElement.value = option |
|
select.appendChild(optionElement) |
|
}) |
|
|
|
select.style = `cursor: pointer; |
|
font-weight: 300; |
|
height: 30px; |
|
min-width: 122px; |
|
position: absolute; |
|
top: 24px; |
|
left: 88px; |
|
z-index: 999999999999999; |
|
` |
|
|
|
return select |
|
} |
|
|
|
function drawCanvasWithText (w, h, tag, color = 'rgba(255,255,255,0.4)') { |
|
const canvas = document.createElement('canvas') |
|
const ctx = canvas.getContext('2d') |
|
|
|
|
|
canvas.width = w |
|
canvas.height = h |
|
|
|
|
|
ctx.fillStyle = color |
|
ctx.fillRect(0, 0, canvas.width, canvas.height) |
|
|
|
|
|
ctx.fillStyle = '#000000' |
|
ctx.font = '20px Arial' |
|
ctx.fillText(tag, 50, 50) |
|
|
|
|
|
const base64 = canvas.toDataURL() |
|
|
|
return base64 |
|
} |
|
|
|
function get_position_style (ctx, widget_width, y, node_height) { |
|
const MARGIN = 4 |
|
|
|
|
|
const elRect = ctx.canvas.getBoundingClientRect() |
|
const transform = new DOMMatrix() |
|
.scaleSelf( |
|
elRect.width / ctx.canvas.width, |
|
elRect.height / ctx.canvas.height |
|
) |
|
.multiplySelf(ctx.getTransform()) |
|
.translateSelf(MARGIN, MARGIN + y) |
|
|
|
return { |
|
transformOrigin: '0 0', |
|
transform: transform, |
|
left: `0`, |
|
top: `0`, |
|
cursor: 'pointer', |
|
position: 'absolute', |
|
maxWidth: `${widget_width - MARGIN * 2}px`, |
|
|
|
width: `${widget_width - MARGIN * 2}px`, |
|
|
|
|
|
display: 'flex', |
|
|
|
|
|
justifyContent: 'space-around' |
|
} |
|
} |
|
|
|
const getLocalData = key => { |
|
let data = {} |
|
try { |
|
data = JSON.parse(localStorage.getItem(key)) || {} |
|
} catch (error) { |
|
return {} |
|
} |
|
return data |
|
} |
|
|
|
function createImage (url) { |
|
let im = new Image() |
|
return new Promise((res, rej) => { |
|
im.onload = () => res(im) |
|
im.src = url |
|
}) |
|
} |
|
|
|
const parseSvg = async svgContent => { |
|
|
|
const tempContainer = document.createElement('div') |
|
tempContainer.innerHTML = svgContent |
|
|
|
|
|
const svgElement = tempContainer.querySelector('svg') |
|
if (!svgElement) return |
|
|
|
var rectElements = svgElement?.querySelectorAll('rect') || [] |
|
|
|
|
|
var data = [] |
|
|
|
Array.from(rectElements, (rectElement, i) => { |
|
|
|
var x = ~~(rectElement.getAttribute('x') || 0) |
|
var y = ~~(rectElement.getAttribute('y') || 0) |
|
var width = ~~rectElement.getAttribute('width') |
|
var height = ~~rectElement.getAttribute('height') |
|
|
|
if (x != undefined && y != undefined && width && height) { |
|
|
|
var canvas = document.createElement('canvas') |
|
canvas.width = width |
|
canvas.height = height |
|
var context = canvas.getContext('2d') |
|
|
|
|
|
var fill = rectElement.getAttribute('fill') |
|
context.fillStyle = fill |
|
context.fillRect(0, 0, width, height) |
|
|
|
|
|
var base64 = canvas.toDataURL() |
|
|
|
|
|
|
|
var rectData = { |
|
x: parseInt(x), |
|
y: parseInt(y), |
|
width: parseInt(width), |
|
height: parseInt(height), |
|
z_index: i + 1, |
|
scale_option: 'width', |
|
image: base64, |
|
mask: base64, |
|
type: 'base64', |
|
_t: 'rect' |
|
} |
|
|
|
|
|
data.push(rectData) |
|
} |
|
}) |
|
|
|
var svgWidth = svgElement.getAttribute('width') |
|
var svgHeight = svgElement.getAttribute('height') |
|
|
|
if (!(svgWidth && svgHeight)) { |
|
|
|
let viewBox = svgElement.viewBox.baseVal |
|
|
|
svgWidth = viewBox.width |
|
svgHeight = viewBox.height |
|
} |
|
|
|
|
|
var canvas = document.createElement('canvas') |
|
canvas.width = svgWidth |
|
canvas.height = svgHeight |
|
var context = canvas.getContext('2d') |
|
|
|
var svgString = new XMLSerializer().serializeToString(svgElement) |
|
var DOMURL = window.URL || window.webkitURL || window |
|
|
|
var svgBlob = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' }) |
|
var url = DOMURL.createObjectURL(svgBlob) |
|
|
|
let img = await createImage(url) |
|
context.drawImage(img, 0, 0) |
|
|
|
let base64 = canvas.toDataURL() |
|
|
|
var rectData = { |
|
x: 0, |
|
y: 0, |
|
width: parseInt(svgWidth), |
|
height: parseInt(svgHeight), |
|
z_index: 0, |
|
scale_option: 'width', |
|
image: base64, |
|
mask: base64, |
|
type: 'base64', |
|
_t: 'canvas' |
|
} |
|
data.push(rectData) |
|
|
|
|
|
console.log('layers', { data, image: base64, svgElement }) |
|
return { data, image: base64, svgElement } |
|
} |
|
|
|
function findImages (nodeId) { |
|
|
|
const n = app.graph.getNodeById(nodeId) |
|
if (n.imgs) { |
|
return n.imgs |
|
} |
|
|
|
|
|
if (n.inputs) { |
|
for (let i = 0; i < n.inputs.length; i++) { |
|
if (n.inputs[i].name === 'image' || n.inputs[i].name === 'images') { |
|
|
|
var linkId = n.inputs[i]?.link |
|
var origin_id = app.graph.links[linkId].origin_id |
|
return findImages(origin_id) |
|
} |
|
} |
|
} |
|
|
|
|
|
return null |
|
} |
|
|
|
async function setArea (cw, ch, topBase64, base64, data, fn) { |
|
let displayHeight = Math.round(window.screen.availHeight * 0.8) |
|
let div = document.createElement('div') |
|
div.innerHTML = ` |
|
<div id='ml_overlay' style='position: absolute;top:0;background: #251f1fc4; |
|
height: 100vh; |
|
z-index:999999; |
|
width: 100%;'> |
|
<img id='ml_video' style='position: absolute; |
|
height: ${displayHeight}px;user-select: none; |
|
-webkit-user-drag: none; |
|
outline: 2px solid #eaeaea; |
|
box-shadow: 8px 9px 17px #575757;' /> |
|
<div id='ml_selection' style='position: absolute; |
|
border: 2px dashed red; |
|
pointer-events: none; |
|
background-image: url("${topBase64}"); |
|
background-repeat: no-repeat; |
|
background-size: cover; |
|
'></div> |
|
<div class="mx_close"> X </div> |
|
</div>` |
|
|
|
document.body.appendChild(div) |
|
|
|
|
|
|
|
|
|
|
|
let img = div.querySelector('#ml_video') |
|
|
|
let selection = div.querySelector('#ml_selection') |
|
let close = div.querySelector('.mx_close') |
|
let startX, startY, endX, endY |
|
let start = false |
|
let setDone = false |
|
|
|
img.src = base64 |
|
|
|
close.style = `cursor: pointer; |
|
position: fixed; |
|
left: 12px; |
|
top: 12px; |
|
z-index: 99999999; |
|
background: black; |
|
width: 44px; |
|
height: 44px; |
|
text-align: center; |
|
line-height: 44px;` |
|
|
|
|
|
|
|
let x = 0, |
|
y = 0, |
|
width = (cw * displayHeight) / ch, |
|
height = displayHeight |
|
|
|
let imgWidth = cw |
|
let imgHeight = ch |
|
|
|
if (data && data.width > 0 && data.height > 0) { |
|
|
|
x = (width * data.x) / imgWidth |
|
y = (height * data.y) / imgHeight |
|
width = (width * data.width) / imgWidth |
|
height = (height * data.height) / imgHeight |
|
} |
|
|
|
selection.style.left = x + 'px' |
|
selection.style.top = y + 'px' |
|
selection.style.width = width + 'px' |
|
selection.style.height = height + 'px' |
|
|
|
|
|
img.addEventListener('mousedown', startSelection) |
|
img.addEventListener('mousemove', updateSelection) |
|
img.addEventListener('mouseup', endSelection) |
|
|
|
const removeDiv = () => { |
|
div.remove() |
|
close.removeEventListener('click', removeDiv) |
|
img.removeEventListener('mousedown', startSelection) |
|
img.removeEventListener('mousemove', updateSelection) |
|
img.removeEventListener('mouseup', endSelection) |
|
img.removeEventListener('mousedown', setDoneCheck) |
|
} |
|
close.addEventListener('click', removeDiv) |
|
|
|
const setDoneCheck = event => { |
|
console.log(setDone) |
|
if (setDone) { |
|
img.addEventListener('mousedown', startSelection) |
|
img.addEventListener('mousemove', updateSelection) |
|
img.addEventListener('mouseup', endSelection) |
|
setDone = false |
|
start = false |
|
startX = event.clientX |
|
startY = event.clientY |
|
} |
|
} |
|
img.addEventListener('mousedown', setDoneCheck) |
|
|
|
function remove () { |
|
img.removeEventListener('mousedown', startSelection) |
|
img.removeEventListener('mousemove', updateSelection) |
|
img.removeEventListener('mouseup', endSelection) |
|
setDone = true |
|
|
|
} |
|
|
|
function startSelection (event) { |
|
if (start == false) { |
|
startX = event.clientX |
|
startY = event.clientY |
|
updateSelection(event) |
|
start = true |
|
} else { |
|
} |
|
} |
|
|
|
function updateSelection (event) { |
|
endX = event.clientX |
|
endY = event.clientY |
|
|
|
|
|
let width = Math.abs(endX - startX) |
|
let height = Math.abs(endY - startY) |
|
let left = Math.min(startX, endX) |
|
let top = Math.min(startY, endY) |
|
|
|
|
|
selection.style.left = left + 'px' |
|
selection.style.top = top + 'px' |
|
selection.style.width = width + 'px' |
|
selection.style.height = height + 'px' |
|
} |
|
|
|
function endSelection (event) { |
|
endX = event.clientX |
|
endY = event.clientY |
|
|
|
|
|
let imgWidth = img.naturalWidth |
|
let imgHeight = img.naturalHeight |
|
|
|
|
|
let realStartX = (startX / img.offsetWidth) * imgWidth |
|
let realStartY = (startY / img.offsetHeight) * imgHeight |
|
|
|
|
|
let realEndX = (endX / img.offsetWidth) * imgWidth |
|
let realEndY = (endY / img.offsetHeight) * imgHeight |
|
|
|
startX = realStartX |
|
startY = realStartY |
|
endX = realEndX |
|
endY = realEndY |
|
|
|
let width = Math.round(Math.abs(endX - startX)) |
|
let height = Math.round(Math.abs(endY - startY)) |
|
let left = Math.round(Math.min(startX, endX)) |
|
let top = Math.round(Math.min(startY, endY)) |
|
|
|
if (width <= 0 && height <= 0) return remove() |
|
|
|
if (fn) fn(left, top, width, height) |
|
|
|
remove() |
|
} |
|
} |
|
|
|
async function setAreaTags (cw, ch, grids, fn) { |
|
let base64 = drawCanvasWithText(cw, ch, '', 'white') |
|
let displayHeight = Math.round(window.screen.availHeight * 0.8) |
|
let div = document.createElement('div') |
|
div.innerHTML = ` |
|
<div id='ml_overlay' style='position: absolute;top:0;background: #251f1fc4; |
|
height: 100vh; |
|
z-index:999999; |
|
width: 100%;'> |
|
<img id='ml_video' style='position: absolute; |
|
height: ${displayHeight}px;user-select: none; |
|
-webkit-user-drag: none; |
|
outline: 2px solid #eaeaea; |
|
box-shadow: 8px 9px 17px #575757;' /> |
|
${Array.from(grids, g => { |
|
const { label: tag, grid } = g |
|
const [dx, dy, dw, dh] = grid |
|
const base64Data = drawCanvasWithText(dw, dh, tag) |
|
|
|
let x = 0, |
|
y = 0, |
|
width = (cw * displayHeight) / ch, |
|
height = displayHeight |
|
|
|
let imgWidth = cw |
|
let imgHeight = ch |
|
|
|
if (dw > 0 && dh > 0) { |
|
// 相同尺寸窗口,恢复选区 |
|
x = (width * dx) / imgWidth |
|
y = (height * dy) / imgHeight |
|
width = (width * dw) / imgWidth |
|
height = (height * dh) / imgHeight |
|
} |
|
|
|
return `<div class='ml_selection' |
|
data-tag="${tag}" |
|
style='position:absolute; |
|
border: 2px dashed red; |
|
pointer-events: none; |
|
background-image: url("${base64Data}"); |
|
background-repeat: no-repeat; |
|
background-size: cover; |
|
left:${x}px; |
|
top:${y}px; |
|
width:${width}px; |
|
height:${height}px; |
|
'></div>` |
|
})} |
|
<div class="mx_close"> X </div> |
|
</div>` |
|
|
|
document.body.appendChild(div) |
|
|
|
const tags = Array.from(grids, g => g.label) |
|
let select = createSelectWithOptions(tags) |
|
document.body.appendChild(select) |
|
|
|
let img = div.querySelector('#ml_video') |
|
|
|
let selections = [...div.querySelectorAll('.ml_selection')] |
|
|
|
let selection = selections.filter( |
|
s => s.getAttribute('data-tag') === select.value |
|
)[0] |
|
|
|
select.addEventListener('change', e => { |
|
selection = selections.filter( |
|
s => s.getAttribute('data-tag') === select.value |
|
)[0] |
|
}) |
|
|
|
|
|
let close = div.querySelector('.mx_close') |
|
let startX, startY, endX, endY |
|
let start = false |
|
let setDone = false |
|
|
|
img.src = base64 |
|
|
|
close.style = `cursor: pointer; |
|
position: fixed; |
|
left: 12px; |
|
top: 12px; |
|
z-index: 99999999; |
|
background: black; |
|
width: 44px; |
|
height: 44px; |
|
text-align: center; |
|
line-height: 44px;` |
|
|
|
|
|
img.addEventListener('mousedown', startSelection) |
|
img.addEventListener('mousemove', updateSelection) |
|
img.addEventListener('mouseup', endSelection) |
|
|
|
const removeDiv = () => { |
|
div.remove() |
|
select?.remove() |
|
close.removeEventListener('click', removeDiv) |
|
img.removeEventListener('mousedown', startSelection) |
|
img.removeEventListener('mousemove', updateSelection) |
|
img.removeEventListener('mouseup', endSelection) |
|
img.removeEventListener('mousedown', setDoneCheck) |
|
} |
|
close.addEventListener('click', removeDiv) |
|
|
|
const setDoneCheck = event => { |
|
console.log(setDone) |
|
if (setDone) { |
|
img.addEventListener('mousedown', startSelection) |
|
img.addEventListener('mousemove', updateSelection) |
|
img.addEventListener('mouseup', endSelection) |
|
setDone = false |
|
start = false |
|
startX = event.clientX |
|
startY = event.clientY |
|
} |
|
} |
|
img.addEventListener('mousedown', setDoneCheck) |
|
|
|
function remove () { |
|
img.removeEventListener('mousedown', startSelection) |
|
img.removeEventListener('mousemove', updateSelection) |
|
img.removeEventListener('mouseup', endSelection) |
|
setDone = true |
|
|
|
} |
|
|
|
function startSelection (event) { |
|
if (start == false) { |
|
startX = event.clientX |
|
startY = event.clientY |
|
updateSelection(event) |
|
start = true |
|
} else { |
|
} |
|
} |
|
|
|
function updateSelection (event) { |
|
endX = event.clientX |
|
endY = event.clientY |
|
|
|
|
|
let width = Math.abs(endX - startX) |
|
let height = Math.abs(endY - startY) |
|
let left = Math.min(startX, endX) |
|
let top = Math.min(startY, endY) |
|
|
|
|
|
selection.style.left = left + 'px' |
|
selection.style.top = top + 'px' |
|
selection.style.width = width + 'px' |
|
selection.style.height = height + 'px' |
|
} |
|
|
|
function endSelection (event) { |
|
endX = event.clientX |
|
endY = event.clientY |
|
|
|
|
|
let imgWidth = img.naturalWidth |
|
let imgHeight = img.naturalHeight |
|
|
|
|
|
let realStartX = (startX / img.offsetWidth) * imgWidth |
|
let realStartY = (startY / img.offsetHeight) * imgHeight |
|
|
|
|
|
let realEndX = (endX / img.offsetWidth) * imgWidth |
|
let realEndY = (endY / img.offsetHeight) * imgHeight |
|
|
|
startX = realStartX |
|
startY = realStartY |
|
endX = realEndX |
|
endY = realEndY |
|
|
|
let width = Math.round(Math.abs(endX - startX)) |
|
let height = Math.round(Math.abs(endY - startY)) |
|
let left = Math.round(Math.min(startX, endX)) |
|
let top = Math.round(Math.min(startY, endY)) |
|
|
|
if (width <= 0 && height <= 0) return remove() |
|
|
|
if (!!fn) fn(select.value, left, top, width, height) |
|
|
|
remove() |
|
} |
|
} |
|
|
|
app.registerExtension({ |
|
name: 'Mixlab.layer.ShowLayer', |
|
async getCustomWidgets (app) { |
|
return { |
|
EDIT (node, inputName, inputData, app) { |
|
|
|
const widget = { |
|
type: inputData[0], |
|
name: inputName, |
|
size: [128, 44], |
|
draw (ctx, node, widget_width, y, widget_height) { |
|
|
|
if (this.input) |
|
Object.assign( |
|
this.input.style, |
|
get_position_style(ctx, widget_width, 32, node.size[1]) |
|
) |
|
}, |
|
computeSize (...args) { |
|
return [128, 44] |
|
}, |
|
async serializeValue (nodeId, widgetIndex) { |
|
let d = getLocalData('_mixlab_edit_layer') |
|
|
|
return d[node.id] |
|
} |
|
} |
|
|
|
node.addCustomWidget(widget) |
|
return widget |
|
} |
|
} |
|
}, |
|
|
|
async beforeRegisterNodeDef (nodeType, nodeData, app) { |
|
if (nodeType.comfyClass == 'ShowLayer') { |
|
const orig_nodeCreated = nodeType.prototype.onNodeCreated |
|
nodeType.prototype.onNodeCreated = async function () { |
|
orig_nodeCreated?.apply(this, arguments) |
|
|
|
const findNode = nodeId => { |
|
let node = app.graph._nodes_by_id[nodeId] |
|
if (node?.type == 'Reroute') { |
|
let linkId = node.inputs.filter(i => i.type == '*')[0].link |
|
nodeId = app.graph.links.filter(link => link.id == linkId)[0] |
|
?.origin_id |
|
return findNode(nodeId) |
|
} else { |
|
return nodeId |
|
} |
|
} |
|
|
|
|
|
const getLayers = async () => { |
|
console.log( |
|
'getLayers1', |
|
this.inputs.filter(ip => ip.name === 'layers') |
|
) |
|
let linkId = this.inputs.filter(ip => ip.name === 'layers')[0].link |
|
let nodeId = app.graph.links?.filter(link => link.id == linkId)[0] |
|
?.origin_id |
|
|
|
if (nodeId) { |
|
nodeId = findNode(nodeId) |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
let d = getLocalData('_mixlab_svg_image') |
|
console.log('test', d[nodeId]) |
|
|
|
if (d[nodeId]) { |
|
let url = d[nodeId] |
|
let dt = await fetch(url) |
|
|
|
let svgStr = await dt.text() |
|
|
|
const { data } = (await parseSvg(svgStr)) || {} |
|
console.log('fetch', data) |
|
return data |
|
} else { |
|
return [] |
|
} |
|
} |
|
|
|
|
|
const setLayer = async (editIndex, layers = null) => { |
|
|
|
let lys = layers || (await getLayers()) |
|
let layer = lys[editIndex] |
|
|
|
|
|
const updateValue = name => { |
|
const x = this.widgets.filter(w => w.name == name)[0] |
|
x.value = layer[name] |
|
} |
|
if (layer) { |
|
Array.from(['x', 'y', 'width', 'height', 'z_index'], n => |
|
updateValue(n) |
|
) |
|
} |
|
} |
|
|
|
let that = this |
|
const save_edit_layer_index = i => { |
|
let data = getLocalData('_mixlab_edit_layer') |
|
data[that.id] = i |
|
localStorage.setItem('_mixlab_edit_layer', JSON.stringify(data)) |
|
} |
|
|
|
await setLayer(0) |
|
save_edit_layer_index(0) |
|
|
|
const edit = this.widgets.filter(w => w.name == 'edit')[0] |
|
|
|
edit.input = $el('div', {}) |
|
edit.input.style = ` |
|
display: flex; |
|
flex-direction:row; |
|
align-items: center; |
|
margin-top: 0;` |
|
|
|
const ip = $el('input', {}) |
|
ip.className = 'comfy-multiline-input' |
|
ip.type = 'number' |
|
ip.min = 0 |
|
ip.step = 1 |
|
ip.max = Math.max(0, (await getLayers()).length - 1) |
|
|
|
|
|
ip.value = 0 |
|
|
|
ip.style = ` |
|
background-color: var(--comfy-input-bg); |
|
color: var(--input-text); |
|
outline: none; |
|
border: none; |
|
padding: 4px; |
|
width: 60%; |
|
cursor: pointer; |
|
height: 24px;` |
|
const label = document.createElement('label') |
|
label.style = 'font-size: 10px;min-width:32px' |
|
label.innerText = 'Layer Index' |
|
edit.input.appendChild(label) |
|
edit.input.appendChild(ip) |
|
|
|
document.body.appendChild(edit.input) |
|
|
|
ip.addEventListener('click', async event => { |
|
console.log(await getLayers()) |
|
ip.max = Math.max(0, (await getLayers()).length - 1) |
|
}) |
|
|
|
ip.addEventListener('change', async event => { |
|
let index = ~~ip.value |
|
let lys = await getLayers() |
|
await setLayer(index, lys) |
|
app.graph.setDirtyCanvas(true, true) |
|
save_edit_layer_index(index) |
|
}) |
|
|
|
|
|
|
|
const onRemoved = this.onRemoved |
|
this.onRemoved = () => { |
|
edit.input.remove() |
|
return onRemoved?.() |
|
} |
|
|
|
if (this.onResize) { |
|
this.onResize(this.size) |
|
} |
|
|
|
this.serialize_widgets = false |
|
} |
|
} |
|
}, |
|
async loadedGraphNode (node, app) { |
|
|
|
|
|
if (node.type === 'SvgImage') { |
|
let widget = node.widgets.filter(w => w.div)[0] |
|
let data = getLocalData('_mixlab_svg_image') |
|
let id = node.id |
|
|
|
|
|
} |
|
} |
|
}) |
|
|
|
app.registerExtension({ |
|
name: 'Mixlab.layer.NewLayer', |
|
async beforeRegisterNodeDef (nodeType, nodeData, app) { |
|
if (nodeData.name === 'NewLayer') { |
|
const orig_nodeCreated = nodeType.prototype.onNodeCreated |
|
nodeType.prototype.onNodeCreated = async function () { |
|
orig_nodeCreated?.apply(this, arguments) |
|
|
|
let b = this.widgets.filter(w => w.type === 'button')[0] |
|
|
|
|
|
if (!b) { |
|
const updateValue = (x1, y1, w1, h1) => { |
|
if (this.widgets) { |
|
for (const widget of this.widgets) { |
|
if (widget.name === 'x') { |
|
widget.value = x1 |
|
} |
|
if (widget.name === 'y') { |
|
widget.value = y1 |
|
} |
|
if (widget.name === 'width') { |
|
widget.value = w1 |
|
} |
|
if (widget.name === 'height') { |
|
widget.value = h1 |
|
} |
|
} |
|
} |
|
} |
|
|
|
this.addWidget('button', 'Set Area', '', () => { |
|
let data = {} |
|
for (const widget of this.widgets) { |
|
if (widget.name === 'x') { |
|
data.x = widget.value |
|
} |
|
if (widget.name === 'y') { |
|
data.y = widget.value |
|
} |
|
if (widget.name === 'width') { |
|
data.width = widget.value |
|
} |
|
if (widget.name === 'height') { |
|
data.height = widget.value |
|
} |
|
} |
|
try { |
|
console.log('this.inputs', this.id) |
|
let imgs = findImages(this.id) |
|
|
|
|
|
|
|
let topIm = imgs[0] |
|
|
|
let linkId = this.inputs[3].link |
|
let nodeId = app.graph.links[linkId].origin_id |
|
|
|
let imgs2 = findImages(nodeId) |
|
let im = imgs2[0] |
|
console.log(topIm, im) |
|
|
|
setArea( |
|
im.naturalWidth, |
|
im.naturalHeight, |
|
topIm.src, |
|
im.src, |
|
data, |
|
updateValue |
|
) |
|
} catch (error) { |
|
console.log(error) |
|
} |
|
}) |
|
} |
|
} |
|
|
|
const onRemoved = this.onRemoved |
|
this.onRemoved = () => { |
|
|
|
|
|
return onRemoved?.() |
|
} |
|
|
|
if (this.onResize) { |
|
this.onResize(this.size) |
|
} |
|
|
|
this.serialize_widgets = true |
|
} |
|
} |
|
}) |
|
|
|
app.registerExtension({ |
|
name: 'Mixlab.layer.GridInput', |
|
async beforeRegisterNodeDef (nodeType, nodeData, app) { |
|
if (nodeType.comfyClass == 'GridInput') { |
|
const orig_nodeCreated = nodeType.prototype.onNodeCreated |
|
nodeType.prototype.onNodeCreated = async function () { |
|
orig_nodeCreated?.apply(this, arguments) |
|
|
|
const grids_widget = this.widgets.filter(w => w.name == 'grids')[0] |
|
|
|
const widget = { |
|
type: 'div', |
|
name: 'upload', |
|
draw (ctx, node, widget_width, y, widget_height) { |
|
Object.assign( |
|
this.div.style, |
|
get_position_style(ctx, widget_width, y, node.size[1]), |
|
{ |
|
justifyContent: 'flex-start' |
|
} |
|
) |
|
} |
|
} |
|
|
|
widget.div = $el('div', {}) |
|
|
|
const addBtn = document.createElement('button') |
|
addBtn.innerText = 'Add Box' |
|
addBtn.style = `cursor: pointer; |
|
font-weight: 300; |
|
margin: 2px; |
|
color: var(--descrip-text); |
|
background-color: var(--comfy-input-bg); |
|
border-radius: 8px; |
|
border-color: var(--border-color); |
|
border-style: solid;height: 30px;min-width: 122px; |
|
` |
|
|
|
const vbtn = document.createElement('button') |
|
vbtn.innerText = 'Set Box' |
|
vbtn.style = `cursor: pointer; |
|
font-weight: 300; |
|
margin: 2px; |
|
color: var(--descrip-text); |
|
background-color: var(--comfy-input-bg); |
|
border-radius: 8px; |
|
border-color: var(--border-color); |
|
border-style: solid;height: 30px;min-width: 122px; |
|
` |
|
|
|
const btn = document.createElement('button') |
|
btn.innerText = 'Upload JSON' |
|
|
|
btn.style = `cursor: pointer; |
|
font-weight: 300; |
|
margin: 2px; |
|
color: var(--descrip-text); |
|
background-color: var(--comfy-input-bg); |
|
border-radius: 8px; |
|
border-color: var(--border-color); |
|
border-style: solid;height: 30px;min-width: 122px; |
|
` |
|
|
|
addBtn.addEventListener('click', () => { |
|
const { width, height, grids } = JSON.parse(grids_widget.value) |
|
grids.push({ |
|
label: 'background', |
|
grid: [12, 12, width - 24, height - 24] |
|
}) |
|
grids_widget.value = JSON.stringify( |
|
{ |
|
width, |
|
height, |
|
grids |
|
}, |
|
null, |
|
2 |
|
) |
|
}) |
|
|
|
vbtn.addEventListener('click', () => { |
|
const { width, height, grids } = JSON.parse(grids_widget.value) |
|
|
|
setAreaTags(width, height, grids, (tag, x, y, w, h) => { |
|
grids_widget.value = JSON.stringify( |
|
{ |
|
width, |
|
height, |
|
grids: Array.from(grids, g => { |
|
if (g.label === tag) { |
|
g.grid = [x, y, w, h] |
|
} |
|
return g |
|
}) |
|
}, |
|
null, |
|
2 |
|
) |
|
}) |
|
}) |
|
|
|
btn.addEventListener('click', () => { |
|
let inp = document.createElement('input') |
|
inp.type = 'file' |
|
inp.accept = '.json' |
|
inp.click() |
|
inp.addEventListener('change', event => { |
|
|
|
const file = event.target.files[0] |
|
this.title = file.name.split('.')[0] |
|
|
|
|
|
|
|
const reader = new FileReader() |
|
|
|
|
|
reader.onload = event => { |
|
|
|
const fileContent = JSON.parse(event.target.result) |
|
const grids = fileContent |
|
grids_widget.value = JSON.stringify(grids, null, 2) |
|
|
|
|
|
inp.remove() |
|
} |
|
|
|
|
|
reader.readAsText(file) |
|
}) |
|
}) |
|
|
|
widget.div.appendChild(addBtn) |
|
widget.div.appendChild(vbtn) |
|
widget.div.appendChild(btn) |
|
document.body.appendChild(widget.div) |
|
this.addCustomWidget(widget) |
|
|
|
const onExecuted = nodeType.prototype.onExecuted |
|
nodeType.prototype.onExecuted = function (message) { |
|
const r = onExecuted?.apply?.(this, arguments) |
|
|
|
let json = message.json |
|
if (json) { |
|
json = { |
|
width: json[0], |
|
height: json[1], |
|
grids: json[2] |
|
} |
|
grids_widget.value = JSON.stringify(json, null, 2) |
|
|
|
} |
|
|
|
return r |
|
} |
|
|
|
const onRemoved = this.onRemoved |
|
this.onRemoved = () => { |
|
widget.div.remove() |
|
return onRemoved?.() |
|
} |
|
|
|
if (this.onResize) { |
|
this.onResize(this.size) |
|
} |
|
|
|
this.serialize_widgets = true |
|
} |
|
} |
|
}, |
|
async loadedGraphNode (node, app) { |
|
if (node.type === 'GridInput') { |
|
try { |
|
const grids_widget = node.widgets.filter(w => w.name == 'grids')[0] |
|
const { width, height, grids } = JSON.parse(grids_widget.value) |
|
console.log('#GridInput', node, grids) |
|
|
|
const div = node.widgets.filter(w => w.name == 'upload')[0] |
|
div.div.querySelector('select').innerHTML = Array.from( |
|
grids, |
|
g => `<option value="${g.label}">${g.label}</option>` |
|
).join('') |
|
} catch (error) {} |
|
} |
|
} |
|
}) |
|
|
|
app.registerExtension({ |
|
name: 'Mixlab.layer.GridDisplayAndSave', |
|
async beforeRegisterNodeDef (nodeType, nodeData, app) { |
|
if (nodeType.comfyClass == 'GridDisplayAndSave') { |
|
const orig_nodeCreated = nodeType.prototype.onNodeCreated |
|
nodeType.prototype.onNodeCreated = async function () { |
|
orig_nodeCreated?.apply(this, arguments) |
|
|
|
const grids_widget = this.widgets.filter(w => w.name == 'grids')[0] |
|
console.log('GridDisplayAndSave', grids_widget) |
|
const widget = { |
|
type: 'div', |
|
name: 'save_json', |
|
draw (ctx, node, widget_width, y, widget_height) { |
|
Object.assign( |
|
this.div.style, |
|
get_position_style(ctx, widget_width, y, node.size[1]), |
|
{ |
|
justifyContent: 'flex-start', |
|
flexDirection: 'column' |
|
} |
|
) |
|
} |
|
} |
|
|
|
widget.div = $el('div', {}) |
|
|
|
const btn = document.createElement('button') |
|
btn.innerText = 'Save JSON' |
|
|
|
btn.style = `cursor: pointer; |
|
font-weight: 300; |
|
margin: 2px; |
|
color: var(--descrip-text); |
|
background-color: var(--comfy-input-bg); |
|
border-radius: 8px; |
|
border-color: var(--border-color); |
|
border-style: solid;height: 30px;min-width: 122px; |
|
max-width: 122px; |
|
` |
|
|
|
btn.addEventListener('click', () => { |
|
if (window._mixlab_grid) |
|
downloadJsonFile( |
|
window._mixlab_grid, |
|
this.widgets.filter(w => w.name == 'filename_prefix')[0]?.value + |
|
'_grid.json' |
|
) |
|
}) |
|
|
|
widget.div.appendChild(btn) |
|
document.body.appendChild(widget.div) |
|
this.addCustomWidget(widget) |
|
|
|
const onExecuted = nodeType.prototype.onExecuted |
|
nodeType.prototype.onExecuted = function (message) { |
|
const r = onExecuted?.apply?.(this, arguments) |
|
let save_json = this.widgets.filter(d => d.name == 'save_json')[0] |
|
let div = save_json?.div |
|
|
|
|
|
let image = message.image[0] |
|
let json = message.json |
|
if (image) { |
|
const { filename, subfolder, type } = image |
|
|
|
if (!div.querySelector('img')) { |
|
let im = new Image() |
|
div.appendChild(im) |
|
im.style.width = '100%' |
|
} |
|
div.querySelector('img').src = api.apiURL( |
|
`/view?filename=${encodeURIComponent( |
|
filename |
|
)}&type=${type}&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}` |
|
) |
|
|
|
window._mixlab_grid = { |
|
width: json[0], |
|
height: json[1], |
|
grids: json[2] |
|
} |
|
|
|
} |
|
|
|
this.onResize?.(this.size) |
|
|
|
return r |
|
} |
|
|
|
const onRemoved = this.onRemoved |
|
this.onRemoved = () => { |
|
widget.div.remove() |
|
return onRemoved?.() |
|
} |
|
|
|
if (this.onResize) { |
|
this.onResize(this.size) |
|
} |
|
|
|
this.serialize_widgets = true |
|
} |
|
} |
|
}, |
|
async loadedGraphNode (node, app) { |
|
if (node.type === 'GridDisplayAndSave') { |
|
try { |
|
let grids_widget = node.widgets.filter(w => w.name === 'grids')[0] |
|
|
|
let uploadWidget = node.widgets.filter(w => w.name == 'upload')[0] |
|
|
|
let grids = JSON.parse(uploadWidget.value) |
|
} catch (error) {} |
|
} |
|
} |
|
}) |
|
|