| 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) {} |
| } |
| } |
| }) |
|
|