Compare commits
12
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f94278776e | ||
|
|
315885cb0b | ||
|
|
7e605f8228 | ||
|
|
aed70435f9 | ||
|
|
1fb1728ede | ||
|
|
497c4fe5a3 | ||
|
|
09ad7764b8 | ||
|
|
bc3d24fddf | ||
|
|
2d634d628a | ||
|
|
790c22d919 | ||
|
|
b464d85c04 | ||
|
|
078618b4cf |
@@ -1,29 +1,19 @@
|
||||
##
|
||||
v0.3.0 🚀🚗🚚🏃
|
||||
v0.4.0 🚀🚗🚚🏃
|
||||
- Add "help" option to the context menu for each node.
|
||||
- Add "find the node" option to the global context menu.
|
||||
- Optimize the 3D Image node and add workflow.
|
||||
|
||||
- Added support for setting proxies: HTTP_PROXY, HTTPS_PROXY, http_proxy, https_proxy ✅
|
||||
### 3D
|
||||

|
||||
[workflow](./workflow/3D-workflow.json)
|
||||
|
||||
- Added a new Speech feature node, enabling the use of a voice assistant: SpeechRecognition & SpeechSynthesis 🎙️
|
||||
|
||||
- Added TextImage node, allowing conversion of text into image format 📷
|
||||
|
||||
- Added SvgImage node, enabling layout parsing and poster generation in conjunction with the Layer class node 🖼️
|
||||
|
||||
- Added an experimental 3DImage node for loading 3D models 🌟
|
||||
|
||||
|
||||

|
||||
|
||||
|
||||
### SpeechRecognition & SpeechSynthesis
|
||||

|
||||
|
||||
[Voice + Real-time Face Swap Workflow](./workflow/语音+实时换脸workflow.json)
|
||||
|
||||
### ScreenShareNode & FloatingVideoNode
|
||||
> Now comfyui supports capturing screen pixel streams from any software and can be used for LCM-Lora integration. Let's get started with implementation and design! 💻🌐
|
||||
|
||||
>
|
||||

|
||||
|
||||
https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43e-410a-ab3a-1952b7b4e7da
|
||||
|
||||
@@ -33,14 +23,10 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
|
||||
|
||||
!! Please use the address with HTTPS (https://127.0.0.1).
|
||||
|
||||
### SpeechRecognition & SpeechSynthesis
|
||||

|
||||
|
||||
### LoadImagesFromLocal
|
||||
> Monitor changes to images in a local folder, and trigger real-time execution of workflows, supporting common image formats, especially PSD format, in conjunction with Photoshop.
|
||||
|
||||

|
||||
|
||||
[workflow-4](./workflow/4-loadfromlocal-watcher-workflow.json)
|
||||
|
||||
[Voice + Real-time Face Swap Workflow](./workflow/语音+实时换脸workflow.json)
|
||||
|
||||
### GPT
|
||||
> Support for calling multiple GPTs.ChatGPT、ChatGLM3 , Some code provided by rui. If you are using OpenAI's service, fill in https://api.openai.com/v1 . If you are using a local LLM service, fill in http://127.0.0.1:xxxx/v1 . Azure OpenAI:https://xxxx.openai.azure.com
|
||||
@@ -50,6 +36,13 @@ https://github.com/shadowcz007/comfyui-mixlab-nodes/assets/12645064/e7e77f90-e43
|
||||
|
||||
[workflow-5](./workflow/5-gpt-workflow.json)
|
||||
|
||||
### LoadImagesFromLocal
|
||||
> Monitor changes to images in a local folder, and trigger real-time execution of workflows, supporting common image formats, especially PSD format, in conjunction with Photoshop.
|
||||
|
||||

|
||||
|
||||
[workflow-4](./workflow/4-loadfromlocal-watcher-workflow.json)
|
||||
|
||||
|
||||
### Layers
|
||||
> A new layer class node has been added, allowing you to separate the image into layers. After merging the images, you can input the controlnet for further processing.
|
||||
@@ -92,8 +85,14 @@ Add edges to an image.
|
||||
|
||||
|
||||
### Improvement
|
||||
|
||||
- Add "help" option to the context menu for each node.
|
||||
- Add "find the node" option to the global context menu.
|
||||
|
||||
An improvement has been made to directly redirect to GitHub to search for missing nodes when loading the graph.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 450 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 255 KiB |
+17
-8
@@ -906,29 +906,38 @@ class Image3D:
|
||||
@classmethod
|
||||
def INPUT_TYPES(s):
|
||||
return {"required": {
|
||||
"upload":("THREED",), },
|
||||
"upload":("THREED",), },
|
||||
}
|
||||
|
||||
RETURN_TYPES = ("IMAGE","MASK",)
|
||||
# RETURN_NAMES = ("IMAGE",)
|
||||
RETURN_TYPES = ("IMAGE","MASK","IMAGE",)
|
||||
RETURN_NAMES = ("IMAGE","MASK","BG_IMAGE",)
|
||||
|
||||
FUNCTION = "run"
|
||||
|
||||
CATEGORY = "♾️Mixlab/image"
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,False,)
|
||||
OUTPUT_IS_LIST = (False,False,False,)
|
||||
|
||||
def run(self,upload):
|
||||
# print(upload['image'])
|
||||
image = base64_to_image(upload['image'])
|
||||
mask = image.split()[3]
|
||||
image=image.convert('RGB')
|
||||
mask=image.convert('L')
|
||||
|
||||
|
||||
mask=mask.convert('L')
|
||||
|
||||
bg_image=None
|
||||
if upload['bg_image']:
|
||||
bg_image = base64_to_image(upload['bg_image'])
|
||||
bg_image=bg_image.convert('RGB')
|
||||
bg_image=pil2tensor(bg_image)
|
||||
|
||||
|
||||
mask=pil2tensor(mask)
|
||||
image=pil2tensor(image)
|
||||
|
||||
return (image,mask,)
|
||||
|
||||
return (image,mask,bg_image,)
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { app } from '../../../scripts/app.js'
|
||||
const repoOwner = 'shadowcz007' // 替换为仓库的所有者
|
||||
const repoName = 'comfyui-mixlab-nodes' // 替换为仓库的名称
|
||||
|
||||
const version = 'v0.3.1'
|
||||
const version = 'v0.4.0'
|
||||
|
||||
fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
|
||||
.then(response => response.json())
|
||||
|
||||
+205
-39
@@ -3,10 +3,15 @@ import { api } from '../../../scripts/api.js'
|
||||
import { ComfyWidgets } from '../../../scripts/widgets.js'
|
||||
import { $el } from '../../../scripts/ui.js'
|
||||
|
||||
async function uploadImage (blob, fileType = '.svg') {
|
||||
|
||||
async function uploadImage (blob, fileType = '.svg', filename) {
|
||||
|
||||
// const blob = await (await fetch(src)).blob();
|
||||
const body = new FormData()
|
||||
body.append('image', new File([blob], new Date().getTime() + fileType))
|
||||
body.append(
|
||||
'image',
|
||||
new File([blob], (filename || new Date().getTime()) + fileType)
|
||||
)
|
||||
|
||||
const resp = await api.fetchApi('/upload/image', {
|
||||
method: 'POST',
|
||||
@@ -228,6 +233,25 @@ const parseSvg = async svgContent => {
|
||||
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) {
|
||||
@@ -441,11 +465,14 @@ app.registerExtension({
|
||||
async serializeValue (nodeId, widgetIndex) {
|
||||
let d = getLocalData('_mixlab_3d_image')
|
||||
// console.log('serializeValue',d)
|
||||
if (d) {
|
||||
let url = d[node.id]
|
||||
if (d && d[node.id]) {
|
||||
let { url, bg } = d[node.id]
|
||||
let base64 = await parseImage(url)
|
||||
let bg_base64 = await parseImage(bg)
|
||||
|
||||
return JSON.parse(JSON.stringify({ image: base64 }))
|
||||
return JSON.parse(
|
||||
JSON.stringify({ image: base64, bg_image: bg_base64 })
|
||||
)
|
||||
} else {
|
||||
return {}
|
||||
}
|
||||
@@ -464,7 +491,7 @@ app.registerExtension({
|
||||
orig_nodeCreated?.apply(this, arguments)
|
||||
|
||||
const uploadWidget = this.widgets.filter(w => w.name == 'upload')[0]
|
||||
console.log('3d nodeData', this.inputs)
|
||||
// console.log('3d nodeData', this.inputs)
|
||||
|
||||
const widget = {
|
||||
type: 'div',
|
||||
@@ -472,12 +499,13 @@ app.registerExtension({
|
||||
draw (ctx, node, widget_width, y, widget_height) {
|
||||
Object.assign(
|
||||
this.div.style,
|
||||
get_position_style(ctx, widget_width, 44, node.size[1])
|
||||
get_position_style(ctx, widget_width, 88, node.size[1])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
widget.div = $el('div', {})
|
||||
widget.div.style.width = `120px`
|
||||
|
||||
document.body.appendChild(widget.div)
|
||||
|
||||
@@ -504,36 +532,44 @@ app.registerExtension({
|
||||
div.appendChild(label)
|
||||
div.appendChild(ip)
|
||||
|
||||
let that = this
|
||||
let that = this,
|
||||
filename = new Date().getTime()
|
||||
|
||||
ip.addEventListener('change', event => {
|
||||
const file = event.target.files[0]
|
||||
const reader = new FileReader()
|
||||
|
||||
filename = new Date().getTime()
|
||||
// 读取文件内容
|
||||
reader.onload = async e => {
|
||||
const fileURL = URL.createObjectURL(file)
|
||||
// console.log('文件URL: ', fileURL)
|
||||
let html = `<model-viewer
|
||||
alt="Neil Armstrong's Spacesuit from the Smithsonian Digitization Programs Office and National Air and Space Museum"
|
||||
src="${fileURL}"
|
||||
ar
|
||||
let html = `<model-viewer src="${fileURL}"
|
||||
min-field-of-view="0deg" max-field-of-view="180deg"
|
||||
shadow-intensity="1"
|
||||
camera-controls
|
||||
touch-action="pan-y">
|
||||
|
||||
<div class="controls">
|
||||
<div>Variant: <select class="variant"></select></div>
|
||||
<div><button class="capture">Capture</button></div>
|
||||
|
||||
<div><button class="bg">BG</button></div>
|
||||
</div></model-viewer>`
|
||||
|
||||
preview.innerHTML = html;
|
||||
that.setSize([that.size[0],that.size[1]+300])
|
||||
app.canvas.draw(true, true)
|
||||
preview.innerHTML = html
|
||||
if (that.size[1] < 400) {
|
||||
that.setSize([that.size[0], that.size[1] + 300])
|
||||
app.canvas.draw(true, true)
|
||||
};
|
||||
|
||||
const modelViewerVariants = preview.querySelector('model-viewer')
|
||||
const select = preview.querySelector('.variant')
|
||||
const capture = preview.querySelector('.capture')
|
||||
const bg = preview.querySelector('.bg')
|
||||
|
||||
if (modelViewerVariants) {
|
||||
modelViewerVariants.style.width = `${that.size[0] - 24}px`
|
||||
modelViewerVariants.style.height = `${that.size[1] - 48}px`
|
||||
}
|
||||
|
||||
|
||||
modelViewerVariants.addEventListener('load', () => {
|
||||
const names = modelViewerVariants.availableVariants
|
||||
@@ -550,29 +586,136 @@ app.registerExtension({
|
||||
select.appendChild(option)
|
||||
})
|
||||
|
||||
select.addEventListener('input', event => {
|
||||
modelViewerVariants.variantName =
|
||||
event.target.value === 'default' ? null : event.target.value
|
||||
})
|
||||
|
||||
capture.addEventListener('click', async () => {
|
||||
let timer = null
|
||||
const delay = 500 // 延迟时间,单位为毫秒
|
||||
|
||||
async function checkCameraChange () {
|
||||
let dd = getLocalData(key)
|
||||
let w, h
|
||||
let base64Data = modelViewerVariants.toDataURL()
|
||||
|
||||
// if (dd[that.id]) {
|
||||
// w = dd[that.id].bg_w
|
||||
// h = dd[that.id].bg_h
|
||||
// }
|
||||
// // 在这里触发相机停止变化的事件
|
||||
// // console.log('在这里触发相机停止变化的事件')
|
||||
// // let base64Data = modelViewerVariants.toDataURL()
|
||||
// if (w && h) {
|
||||
// base64Data = await exportModelViewerImage(
|
||||
// modelViewerVariants.displaycanvas,
|
||||
// w,
|
||||
// h
|
||||
// )
|
||||
// } else {
|
||||
|
||||
// }
|
||||
|
||||
const contentType = getContentTypeFromBase64(base64Data)
|
||||
|
||||
const blob = await base64ToBlobFromURL(base64Data, contentType)
|
||||
|
||||
// const fileBlob = new Blob([e.target.result], { type: file.type });
|
||||
let url = await uploadImage(blob, '.png')
|
||||
console.log(url)
|
||||
// console.log(url)
|
||||
|
||||
let dd = getLocalData(key)
|
||||
dd[that.id] = url
|
||||
if (!dd[that.id]) dd[that.id] = { url, bg: '' }
|
||||
dd[that.id] = { ...dd[that.id], url }
|
||||
|
||||
setLocalDataOfWin(key, dd)
|
||||
}
|
||||
|
||||
function startTimer () {
|
||||
if (timer) clearTimeout(timer)
|
||||
timer = setTimeout(checkCameraChange, delay)
|
||||
}
|
||||
|
||||
modelViewerVariants.addEventListener('camera-change', startTimer)
|
||||
|
||||
select.addEventListener('input', event => {
|
||||
modelViewerVariants.variantName =
|
||||
event.target.value === 'default' ? null : event.target.value
|
||||
checkCameraChange()
|
||||
})
|
||||
|
||||
bg.addEventListener('click', () => {
|
||||
// 创建一个input元素
|
||||
var input = document.createElement('input')
|
||||
input.type = 'file'
|
||||
|
||||
// 监听input的change事件
|
||||
input.addEventListener('change', function () {
|
||||
// 获取上传的文件
|
||||
var file = input.files[0]
|
||||
|
||||
// 创建一个FileReader对象来读取文件
|
||||
var reader = new FileReader()
|
||||
|
||||
// 监听FileReader的load事件
|
||||
reader.addEventListener('load', async () => {
|
||||
let base64 = reader.result
|
||||
// 将读取的文件内容设置为div的背景
|
||||
preview.style.backgroundImage = 'url(' + base64 + ')'
|
||||
|
||||
const contentType = getContentTypeFromBase64(base64)
|
||||
|
||||
const blob = await base64ToBlobFromURL(base64, contentType)
|
||||
|
||||
// const fileBlob = new Blob([e.target.result], { type: file.type });
|
||||
let bg_url = await uploadImage(blob, '.png')
|
||||
let bg_img = await createImage(base64)
|
||||
|
||||
let dd = getLocalData(key)
|
||||
// console.log(dd[that.id],bg_url)
|
||||
if (!dd[that.id]) dd[that.id] = { url: '', bg: bg_url }
|
||||
dd[that.id] = {
|
||||
...dd[that.id],
|
||||
bg: bg_url,
|
||||
bg_w: bg_img.naturalWidth,
|
||||
bg_h: bg_img.naturalHeight
|
||||
}
|
||||
|
||||
setLocalDataOfWin(key, dd)
|
||||
|
||||
// 更新尺寸
|
||||
let w = that.size[0] - 24,
|
||||
h = (w * bg_img.naturalHeight) / bg_img.naturalWidth
|
||||
|
||||
if (modelViewerVariants) {
|
||||
modelViewerVariants.style.width = `${w}px`
|
||||
modelViewerVariants.style.height = `${h}px`
|
||||
}
|
||||
preview.style.width = `${w}px`
|
||||
|
||||
})
|
||||
|
||||
// 读取文件
|
||||
reader.readAsDataURL(file)
|
||||
})
|
||||
|
||||
// 触发input的点击事件
|
||||
input.click()
|
||||
})
|
||||
|
||||
uploadWidget.value = await uploadWidget.serializeValue()
|
||||
|
||||
// 更新尺寸
|
||||
let dd = getLocalData(key)
|
||||
// console.log(dd[that.id],bg_url)
|
||||
if (dd[that.id]) {
|
||||
const { bg_w, bg_h } = dd[that.id]
|
||||
if (bg_h && bg_w) {
|
||||
let w = that.size[0] - 24,
|
||||
h = (w * bg_h) / bg_w
|
||||
|
||||
if (modelViewerVariants) {
|
||||
modelViewerVariants.style.width = `${w}px`
|
||||
modelViewerVariants.style.height = `${h}px`
|
||||
}
|
||||
preview.style.width = `${w}px`
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 以文本形式读取文件
|
||||
@@ -585,23 +728,39 @@ app.registerExtension({
|
||||
preview.className = 'preview'
|
||||
preview.style = `margin-top: 12px;display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;`
|
||||
align-items: center;background-repeat: no-repeat;background-size: contain;`
|
||||
|
||||
let upload = inputDiv('_mixlab_3d_image', '3D Model', preview)
|
||||
|
||||
widget.div.appendChild(upload)
|
||||
widget.div.appendChild(preview)
|
||||
this.addCustomWidget(widget);
|
||||
this.addCustomWidget(widget)
|
||||
|
||||
const onResize = this.onResize;
|
||||
this.onResize = function () {
|
||||
let m=preview.querySelector('model-viewer')
|
||||
m.style.width=`${this.size[0]-24}px`
|
||||
m.style.height=`${this.size[1]-48}px`
|
||||
// console.log(this.size,preview)
|
||||
return onResize?.apply(this, arguments);
|
||||
};
|
||||
const onResize = this.onResize
|
||||
let that=this;
|
||||
this.onResize = function () {
|
||||
|
||||
let modelViewerVariants = preview.querySelector('model-viewer')
|
||||
|
||||
// 更新尺寸
|
||||
let dd = getLocalData('_mixlab_3d_image')
|
||||
// console.log(dd[that.id],bg_url)
|
||||
if (dd[that.id]) {
|
||||
const { bg_w, bg_h } = dd[that.id]
|
||||
if (bg_h && bg_w) {
|
||||
let w = that.size[0] - 24,
|
||||
h = (w * bg_h) / bg_w
|
||||
|
||||
if (modelViewerVariants) {
|
||||
modelViewerVariants.style.width = `${w}px`
|
||||
modelViewerVariants.style.height = `${h}px`
|
||||
}
|
||||
preview.style.width = `${w}px`
|
||||
}
|
||||
}
|
||||
|
||||
return onResize?.apply(this, arguments)
|
||||
}
|
||||
|
||||
const onRemoved = this.onRemoved
|
||||
this.onRemoved = () => {
|
||||
@@ -634,13 +793,20 @@ app.registerExtension({
|
||||
let dd = getLocalData('_mixlab_3d_image')
|
||||
|
||||
let id = node.id
|
||||
console.log('3dImage load', node.widgets[0], node.widgets)
|
||||
// console.log('3dImage load', node.widgets[0], node.widgets)
|
||||
if (!dd[id]) return
|
||||
|
||||
let url = dd[id]
|
||||
let { url, bg } = dd[id]
|
||||
if (!url) return
|
||||
// let base64 = await parseImage(url)
|
||||
|
||||
widget.div.querySelector('.preview').innerHTML = `<img src="${url}"/>`
|
||||
let pre = widget.div.querySelector('.preview')
|
||||
pre.style.width = `${node.size[0]}px`
|
||||
pre.innerHTML = `
|
||||
${url ? `<img src="${url}" style="width:100%"/>` : ''}
|
||||
`
|
||||
pre.style.backgroundImage = 'url(' + bg + ')'
|
||||
|
||||
|
||||
const uploadWidget = node.widgets.filter(w => w.name == 'upload')[0]
|
||||
uploadWidget.value = await uploadWidget.serializeValue()
|
||||
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user