|
import { app } from '../../../scripts/app.js' |
|
import { api } from '../../../scripts/api.js' |
|
|
|
import { $el } from '../../../scripts/ui.js' |
|
import { applyTextReplacements } from '../../../scripts/utils.js' |
|
|
|
function loadImageToCanvas (base64Image) { |
|
var img = new Image() |
|
var canvas = document.createElement('canvas') |
|
var ctx = canvas.getContext('2d') |
|
return new Promise((res, rej) => { |
|
img.onload = function () { |
|
|
|
var width = img.width |
|
var height = img.height |
|
var max_width = 1024 |
|
if (width > max_width) { |
|
height *= max_width / width |
|
width = max_width |
|
} |
|
|
|
|
|
canvas.width = width |
|
canvas.height = height |
|
|
|
|
|
ctx.drawImage(img, 0, 0, width, height) |
|
|
|
|
|
var canvasData = canvas.toDataURL() |
|
res(canvasData) |
|
} |
|
|
|
img.src = base64Image |
|
}) |
|
} |
|
|
|
async function uploadImage (blob, fileType = '.svg', filename) { |
|
|
|
const body = new FormData() |
|
body.append( |
|
'image', |
|
new File([blob], (filename || new Date().getTime()) + fileType) |
|
) |
|
|
|
const resp = await api.fetchApi('/upload/image', { |
|
method: 'POST', |
|
body |
|
}) |
|
|
|
|
|
let data = await resp.json() |
|
let { name, subfolder } = data |
|
let src = api.apiURL( |
|
`/view?filename=${encodeURIComponent( |
|
name |
|
)}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}` |
|
) |
|
|
|
return src |
|
} |
|
|
|
const base64Df = |
|
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAAAXNSR0IArs4c6QAAALZJREFUKFOFkLERwjAQBPdbgBkInECGaMLUQDsE0AkRVRAYWqAByxldPPOWHwnw4OBGye1p50UDSoA+W2ABLPN7i+C5dyC6R/uiAUXRQCs0bXoNIu4QPQzAxDKxHoALOrZcqtiyR/T6CXw7+3IGHhkYcy6BOR2izwT8LptG8rbMiCRAUb+CQ6WzQVb0SNOi5Z2/nX35DRyb/ENazhpWKoGwrpD6nICp5c2qogc4of+c7QcrhgF4Aa/aoAFHiL+RAAAAAElFTkSuQmCC' |
|
|
|
function base64ToBlobFromURL (base64URL, contentType) { |
|
return fetch(base64URL).then(response => response.blob()) |
|
} |
|
|
|
function getContentTypeFromBase64 (base64Data) { |
|
const regex = /^data:(.+);base64,/ |
|
const matches = base64Data.match(regex) |
|
if (matches && matches.length >= 2) { |
|
return matches[1] |
|
} |
|
return null |
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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', |
|
flexDirection: 'column', |
|
|
|
justifyContent: 'space-around' |
|
} |
|
} |
|
|
|
const getLocalData = key => { |
|
let data = {} |
|
try { |
|
data = JSON.parse(localStorage.getItem(key)) || {} |
|
} catch (error) { |
|
return {} |
|
} |
|
return data |
|
} |
|
|
|
const setLocalDataOfWin = (key, value) => { |
|
localStorage.setItem(key, JSON.stringify(value)) |
|
|
|
} |
|
|
|
function createImage (url) { |
|
let im = new Image() |
|
return new Promise((res, rej) => { |
|
im.onload = () => res(im) |
|
im.src = url |
|
}) |
|
} |
|
|
|
const parseImageToBase64 = url => { |
|
return new Promise((res, rej) => { |
|
fetch(url) |
|
.then(response => response.blob()) |
|
.then(blob => { |
|
const reader = new FileReader() |
|
reader.onloadend = () => { |
|
const base64data = reader.result |
|
res(base64data) |
|
|
|
} |
|
reader.readAsDataURL(blob) |
|
}) |
|
.catch(error => { |
|
console.log('发生错误:', error) |
|
}) |
|
}) |
|
} |
|
|
|
const parseSvg = async svgContent => { |
|
let scale = 2 |
|
|
|
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 |
|
} else { |
|
try { |
|
svgWidth = ~~svgWidth.replace('px', '') |
|
svgHeight = ~~svgHeight.replace('px', '') |
|
} catch (error) {} |
|
} |
|
|
|
|
|
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 exportModelViewerImage ( |
|
modelViewer, |
|
width, |
|
height, |
|
format = 'image/png', |
|
quality = 1.0 |
|
) { |
|
const canvas = document.createElement('canvas') |
|
canvas.width = width |
|
canvas.height = height |
|
const context = canvas.getContext('2d') |
|
|
|
return new Promise((resolve, reject) => { |
|
context.drawImage(modelViewer, 0, 0, width, height) |
|
|
|
resolve(canvas.toDataURL(format, quality)) |
|
}) |
|
} |
|
|
|
app.registerExtension({ |
|
name: 'Mixlab.image.SvgImage', |
|
async getCustomWidgets (app) { |
|
return { |
|
SVG (node, inputName, inputData, app) { |
|
|
|
const widget = { |
|
type: inputData[0], |
|
name: inputName, |
|
size: [128, 88], |
|
draw (ctx, node, width, y) {}, |
|
computeSize (...args) { |
|
return [128, 88] |
|
}, |
|
async serializeValue (nodeId, widgetIndex) { |
|
let d = getLocalData('_mixlab_svg_image') |
|
|
|
if (d) { |
|
let url = d[node.id] |
|
let dt = await fetch(url) |
|
let svgStr = await dt.text() |
|
const { data, image } = (await parseSvg(svgStr)) || {} |
|
|
|
return JSON.parse(JSON.stringify({ data, image })) |
|
} else { |
|
return |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
node.addCustomWidget(widget) |
|
|
|
return widget |
|
} |
|
} |
|
}, |
|
|
|
async beforeRegisterNodeDef (nodeType, nodeData, app) { |
|
if (nodeType.comfyClass == 'SvgImage') { |
|
const orig_nodeCreated = nodeType.prototype.onNodeCreated |
|
nodeType.prototype.onNodeCreated = async function () { |
|
orig_nodeCreated?.apply(this, arguments) |
|
|
|
const uploadWidget = this.widgets.filter(w => w.name == 'upload')[0] |
|
|
|
|
|
const widget = { |
|
type: 'div', |
|
name: 'upload-preview', |
|
draw (ctx, node, widget_width, y, widget_height) { |
|
Object.assign( |
|
this.div.style, |
|
get_position_style(ctx, widget_width, 44, node.size[1]) |
|
) |
|
} |
|
} |
|
|
|
widget.div = $el('div', {}) |
|
|
|
document.body.appendChild(widget.div) |
|
|
|
const inputDiv = (key, placeholder, svgContainer) => { |
|
let div = document.createElement('div') |
|
const ip = document.createElement('input') |
|
ip.type = 'file' |
|
ip.className = `${'comfy-multiline-input'} ${placeholder}` |
|
div.style = `display: flex; |
|
align-items: center; |
|
margin: 6px 8px; |
|
margin-top: 0;` |
|
ip.placeholder = placeholder |
|
|
|
|
|
ip.style = `outline: none; |
|
border: none; |
|
padding: 4px; |
|
width: 60%;cursor: pointer; |
|
height: 32px;` |
|
const label = document.createElement('label') |
|
label.style = 'font-size: 10px;min-width:32px' |
|
label.innerText = placeholder |
|
div.appendChild(label) |
|
div.appendChild(ip) |
|
|
|
let that = this |
|
|
|
ip.addEventListener('change', event => { |
|
const file = event.target.files[0] |
|
const reader = new FileReader() |
|
|
|
|
|
reader.onload = async e => { |
|
const svgContent = e.target.result |
|
|
|
var blob = new Blob([svgContent], { type: 'image/svg+xml' }) |
|
let url = await uploadImage(blob) |
|
|
|
const { svgElement, data, image } = await parseSvg(svgContent) |
|
|
|
let dd = getLocalData(key) |
|
dd[that.id] = url |
|
setLocalDataOfWin(key, dd) |
|
|
|
|
|
svgElement.style = `width: 90%;padding: 5%;height: auto;` |
|
|
|
|
|
svgContainer.innerHTML = '' |
|
svgContainer.appendChild(svgElement) |
|
let h = ~~getComputedStyle(svgElement).height.replace('px', '') |
|
if (that.size && that.size[1] < h) { |
|
that.setSize([that.size[0], that.size[1] + h]) |
|
app.canvas.draw(true, true) |
|
} |
|
|
|
|
|
uploadWidget.value = await uploadWidget.serializeValue() |
|
} |
|
|
|
|
|
reader.readAsText(file) |
|
}) |
|
return div |
|
} |
|
|
|
let svg = document.createElement('div') |
|
svg.className = 'preview' |
|
svg.style = `background:#eee;margin-top: 12px;` |
|
|
|
let upload = inputDiv('_mixlab_svg_image', 'Svg', svg) |
|
|
|
widget.div.appendChild(upload) |
|
widget.div.appendChild(svg) |
|
this.addCustomWidget(widget) |
|
|
|
const onRemoved = this.onRemoved |
|
this.onRemoved = () => { |
|
upload.remove() |
|
svg.remove() |
|
widget.div.remove() |
|
return onRemoved?.() |
|
} |
|
|
|
if (this.onResize) { |
|
this.onResize(this.size) |
|
} |
|
|
|
this.serialize_widgets = true |
|
} |
|
} |
|
}, |
|
async loadedGraphNode (node, app) { |
|
|
|
|
|
const sleep = (t = 1000) => { |
|
return new Promise((res, rej) => { |
|
setTimeout(() => res(1), t) |
|
}) |
|
} |
|
if (node.type === 'SvgImage') { |
|
|
|
let widget = node.widgets.filter(w => w.name === 'upload-preview')[0] |
|
|
|
let dd = getLocalData('_mixlab_svg_image') |
|
|
|
let id = node.id |
|
console.log('SvgImage load', node.widgets[0], node.widgets) |
|
if (!dd[id]) return |
|
let dt = await fetch(dd[id]) |
|
let svgStr = await dt.text() |
|
|
|
const { svgElement, data, image } = await parseSvg(svgStr) |
|
svgElement.style = `width: 90%;padding: 5%;height:auto` |
|
|
|
|
|
widget.div.querySelector('.preview').innerHTML = '' |
|
widget.div.querySelector('.preview').appendChild(svgElement) |
|
|
|
const uploadWidget = node.widgets.filter(w => w.name == 'upload')[0] |
|
uploadWidget.value = await uploadWidget.serializeValue() |
|
} |
|
} |
|
}) |
|
|
|
const createSelect = (imgDiv, select, opts, targetWidget, textWidget) => { |
|
select.style.display = 'block' |
|
let html = '' |
|
let isMatch = false |
|
for (const opt of opts) { |
|
html += `<option value='${opt.keyword}' ${opt.selected ? 'selected' : ''}>${ |
|
opt.keyword |
|
}</option>` |
|
if (opt.selected) { |
|
isMatch = true |
|
imgDiv.src = opt.imgurl |
|
|
|
} |
|
} |
|
select.innerHTML = html |
|
if (!isMatch) { |
|
|
|
imgDiv.src = opts[0].imgurl |
|
} |
|
|
|
|
|
select.addEventListener('change', async function () { |
|
|
|
var selectedOption = select.options[select.selectedIndex].value |
|
let t = opts.filter(opt => opt.keyword === selectedOption)[0] |
|
|
|
targetWidget.value = await parseImageToBase64(t.imgurl) |
|
imgDiv.src = targetWidget.value |
|
textWidget.value = t.keyword |
|
}) |
|
|
|
} |
|
|
|
app.registerExtension({ |
|
name: 'Mixlab.prompt.ImagesPrompt_', |
|
async beforeRegisterNodeDef (nodeType, nodeData, app) { |
|
if (nodeType.comfyClass == 'ImagesPrompt_') { |
|
const orig_nodeCreated = nodeType.prototype.onNodeCreated |
|
nodeType.prototype.onNodeCreated = async function () { |
|
orig_nodeCreated?.apply(this, arguments) |
|
|
|
const image_prompt = this.widgets.filter( |
|
w => w.name == 'image_base64' |
|
)[0] |
|
const image_text = this.widgets.filter(w => w.name == 'text')[0] |
|
|
|
const node = this |
|
|
|
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]) |
|
) |
|
} |
|
} |
|
|
|
widget.div = $el('div', {}) |
|
|
|
|
|
const img = new Image() |
|
img.src = image_prompt?.value || base64Df |
|
widget.div.appendChild(img) |
|
|
|
const btn = document.createElement('button') |
|
btn.innerText = 'Upload Images 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; |
|
` |
|
|
|
const select = document.createElement('select') |
|
select.style = `display:none;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: 100px; |
|
` |
|
widget.select = select |
|
|
|
|
|
|
|
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 = async event => { |
|
|
|
const json = JSON.parse(event.target.result) |
|
console.log(node, json) |
|
|
|
widget.value = JSON.stringify(json) |
|
|
|
let img = widget.div.querySelector('img') |
|
|
|
createSelect(img, select, json, image_prompt, image_text) |
|
|
|
image_prompt.value = await parseImageToBase64(json[0].imgurl) |
|
image_text.value = json[0].keyword |
|
|
|
if (img) { |
|
img.src = image_prompt.value |
|
} |
|
|
|
inp.remove() |
|
} |
|
|
|
|
|
reader.readAsText(file) |
|
}) |
|
}) |
|
|
|
widget.div.appendChild(btn) |
|
widget.div.appendChild(select) |
|
document.body.appendChild(widget.div) |
|
this.addCustomWidget(widget) |
|
|
|
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 === 'ImagesPrompt_') { |
|
try { |
|
let prompt = node.widgets.filter(w => w.name === 'image_base64')[0] |
|
let text = node.widgets.filter(w => w.name === 'text')[0] |
|
let uploadWidget = node.widgets.filter(w => w.name == 'upload')[0] |
|
|
|
let img = uploadWidget.div.querySelector('img') |
|
let json = JSON.parse(uploadWidget.value) |
|
|
|
for (let index = 0; index < json.length; index++) { |
|
const j = json[index] |
|
let base64 = await parseImageToBase64(j.imgurl) |
|
if (base64 === prompt.value) { |
|
json[index].selected = true |
|
} |
|
} |
|
|
|
if (json && json[0]) { |
|
uploadWidget.select.style.display = 'block' |
|
createSelect(img, uploadWidget.select, json, prompt, text) |
|
} |
|
} catch (error) {} |
|
} |
|
} |
|
}) |
|
|
|
const createInputImageForBatch = (base64, widget) => { |
|
let im = new Image() |
|
im.src = base64 |
|
im.style = `width: 88px;` |
|
|
|
im.addEventListener('click', e => { |
|
let newValue = [] |
|
let items = widget.value?.base64 || [] |
|
for (const v of items) { |
|
if (v != base64) newValue.push(v) |
|
} |
|
widget.value.base64 = newValue |
|
im.remove() |
|
}) |
|
|
|
return im |
|
} |
|
|
|
app.registerExtension({ |
|
name: 'Mixlab.Comfy.LoadImagesToBatch', |
|
async getCustomWidgets (app) { |
|
return { |
|
IMAGEBASE64 (node, inputName, inputData, app) { |
|
|
|
const widget = { |
|
value: { |
|
base64: [] |
|
}, |
|
type: inputData[0], |
|
name: inputName, |
|
size: [128, 32], |
|
draw (ctx, node, width, y) {}, |
|
computeSize (...args) { |
|
return [128, 32] |
|
} |
|
|
|
|
|
|
|
} |
|
|
|
node.addCustomWidget(widget) |
|
return widget |
|
} |
|
} |
|
}, |
|
|
|
async beforeRegisterNodeDef (nodeType, nodeData, app) { |
|
if (nodeType.comfyClass == 'LoadImagesToBatch') { |
|
|
|
const orig_nodeCreated = nodeType.prototype.onNodeCreated |
|
|
|
nodeType.prototype.onNodeCreated = function () { |
|
orig_nodeCreated?.apply(this, arguments) |
|
|
|
let imagesWidget = this.widgets.filter(w => w.name == 'images')[0] |
|
|
|
const widget = { |
|
type: 'div', |
|
name: 'image_base64', |
|
draw (ctx, node, widget_width, y, widget_height) { |
|
Object.assign( |
|
this.div.style, |
|
get_position_style(ctx, widget_width, 44, node.size[1]) |
|
) |
|
}, |
|
serialize: false |
|
} |
|
|
|
widget.div = $el('div', {}) |
|
|
|
document.body.appendChild(widget.div) |
|
|
|
let imagePreview = document.createElement('div') |
|
let imagesDiv = document.createElement('div') |
|
imagesDiv.className = 'images_preview' |
|
imagesDiv.style = `width: calc(100% - 14px); |
|
display: flex; |
|
flex-wrap: wrap; |
|
padding: 7px; justify-content: space-between; |
|
align-items: center;` |
|
|
|
let inputImage = document.createElement('input') |
|
inputImage.type = 'file' |
|
inputImage.style.display = 'none' |
|
inputImage.addEventListener('change', e => { |
|
e.preventDefault() |
|
const file = e.target.files[0] |
|
const reader = new FileReader() |
|
reader.onload = async event => { |
|
let base64 = event.target.result |
|
|
|
base64 = await loadImageToCanvas(base64) |
|
|
|
if (!imagesWidget.value) imagesWidget.value = { base64: [] } |
|
imagesWidget.value.base64.push(base64) |
|
let im = createInputImageForBatch(base64, imagesWidget) |
|
imagesDiv.appendChild(im) |
|
} |
|
reader.readAsDataURL(file) |
|
}) |
|
|
|
const btn = document.createElement('button') |
|
btn.innerText = 'Upload Image' |
|
|
|
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; |
|
` |
|
|
|
btn.addEventListener('click', e => { |
|
e.preventDefault() |
|
inputImage.click() |
|
}) |
|
|
|
widget.div.appendChild(imagePreview) |
|
imagePreview.appendChild(imagesDiv) |
|
imagePreview.appendChild(btn) |
|
imagePreview.appendChild(inputImage) |
|
|
|
this.addCustomWidget(widget) |
|
|
|
|
|
|
|
const onRemoved = this.onRemoved |
|
this.onRemoved = () => { |
|
inputImage.remove() |
|
widget.div.remove() |
|
try { |
|
|
|
} catch (error) { |
|
console.log(error) |
|
} |
|
|
|
return onRemoved?.() |
|
} |
|
|
|
this.serialize_widgets = true |
|
} |
|
} |
|
|
|
if (nodeData.name === 'SaveImageAndMetadata_') { |
|
const onNodeCreated = nodeType.prototype.onNodeCreated |
|
|
|
nodeType.prototype.onNodeCreated = function () { |
|
const r = onNodeCreated |
|
? onNodeCreated.apply(this, arguments) |
|
: undefined |
|
const widget = this.widgets.find(w => w.name === 'filename_prefix') |
|
widget.serializeValue = () => { |
|
return applyTextReplacements(app, widget.value) |
|
} |
|
|
|
return r |
|
} |
|
|
|
const onExecuted = nodeType.prototype.onExecuted |
|
nodeType.prototype.onExecuted = function (message) { |
|
onExecuted?.apply(this, arguments) |
|
console.log('##onExecuted', this, message) |
|
|
|
if (message.base64) { |
|
if (Array.isArray(message.base64)) { |
|
} |
|
} |
|
} |
|
} |
|
}, |
|
async loadedGraphNode (node, app) { |
|
if (node.type === 'LoadImagesToBatch') { |
|
|
|
let imagesWidget = node.widgets.filter(w => w.name === 'images')[0] |
|
let imagePreview = node.widgets.filter(w => w.name == 'image_base64')[0] |
|
|
|
let pre = imagePreview.div.querySelector('.images_preview') |
|
for (const d of imagesWidget.value?.base64 || []) { |
|
let im = createInputImageForBatch(d, imagesWidget) |
|
pre.appendChild(im) |
|
} |
|
} |
|
} |
|
}) |
|
|
|
|
|
app.registerExtension({ |
|
name: 'Mixlab.output.ComparingTwoFrames_', |
|
init () { |
|
$el('link', { |
|
rel: 'stylesheet', |
|
href: '/extensions/comfyui-mixlab-nodes/lib/juxtapose.css', |
|
parent: document.head |
|
}) |
|
|
|
$el('style', { |
|
textContent: ` |
|
.juxtapose-name{ |
|
display: none!important; |
|
} |
|
`, |
|
parent: document.body |
|
}) |
|
}, |
|
async beforeRegisterNodeDef (nodeType, nodeData, app) { |
|
if (nodeType.comfyClass == 'ComparingTwoFrames_') { |
|
const onNodeCreated = nodeType.prototype.onNodeCreated |
|
nodeType.prototype.onNodeCreated = function () { |
|
const r = onNodeCreated |
|
? onNodeCreated.apply(this, arguments) |
|
: undefined |
|
|
|
this.size = [400, this.size[1]] |
|
console.log('##onNodeCreated', this) |
|
const widget = { |
|
type: 'div', |
|
name: 'preview', |
|
draw (ctx, node, widget_width, y, widget_height) { |
|
Object.assign( |
|
this.div.style, |
|
get_position_style(ctx, 400, 44, node.size[1]) |
|
) |
|
}, |
|
serialize: false |
|
} |
|
|
|
widget.div = $el('div', {}) |
|
|
|
document.body.appendChild(widget.div) |
|
this.addCustomWidget(widget) |
|
this.serialize_widgets = true |
|
|
|
|
|
const onRemoved = this.onRemoved |
|
this.onRemoved = () => { |
|
widget.div.remove() |
|
return onRemoved?.() |
|
} |
|
|
|
|
|
return r |
|
|
|
} |
|
|
|
|
|
|
|
const onExecuted = nodeType.prototype.onExecuted |
|
nodeType.prototype.onExecuted = function (message) { |
|
onExecuted?.apply(this, arguments) |
|
console.log('##onExecuted', this, message) |
|
|
|
this.widgets[0].div.id = 'mix_comparingtowframes_' + this.id |
|
|
|
let after_image = message.after_images[0] |
|
let before_image = message.before_images[0] |
|
|
|
after_image = `${window.location.protocol}//${ |
|
window.location.hostname |
|
}:${window.location.port}/view?filename=${encodeURIComponent( |
|
after_image.filename |
|
)}&type=${after_image.type}&subfolder=${encodeURIComponent( |
|
after_image.subfolder |
|
)}&t=${+new Date()}` |
|
|
|
before_image = `${window.location.protocol}//${ |
|
window.location.hostname |
|
}:${window.location.port}/view?filename=${encodeURIComponent( |
|
before_image.filename |
|
)}&type=${before_image.type}&subfolder=${encodeURIComponent( |
|
before_image.subfolder |
|
)}&t=${+new Date()}` |
|
|
|
this.widgets[0].div.innerHTML = '' |
|
|
|
let slider = new juxtapose.JXSlider( |
|
'#mix_comparingtowframes_' + this.id, |
|
[ |
|
{ |
|
src: before_image, |
|
label: 'Before' |
|
}, |
|
{ |
|
src: after_image, |
|
label: 'After' |
|
} |
|
], |
|
{ |
|
animate: true, |
|
showLabels: true, |
|
showCredits: false, |
|
startingPosition: '50%', |
|
makeResponsive: false |
|
} |
|
) |
|
|
|
this.widgets_values = [ |
|
{ |
|
src: before_image, |
|
label: 'Before' |
|
}, |
|
{ |
|
src: after_image, |
|
label: 'After' |
|
} |
|
] |
|
this.size=[this.size[0],300] |
|
} |
|
} |
|
}, |
|
async loadedGraphNode (node, app) { |
|
|
|
if (node.type === 'ComparingTwoFrames_') { |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
} |
|
} |
|
}) |
|
|