Compare commits

..
10 Commits
Author SHA1 Message Date
shadowcz007 d49baa1540 Update checkVersion_mixlab.js 2024-04-15 23:40:01 +08:00
shadowcz007 5c686af842 Update ImageNode.py 2024-04-15 09:02:49 +08:00
shadowcz007 22425b5bc6 Update index.html 2024-04-13 00:12:05 +08:00
shadowcz007 b6d9b338d2 Update index.html 2024-04-12 21:55:56 +08:00
shadowcz007 a191a13751 Update index.html 2024-04-12 21:48:31 +08:00
shadowcz007 3eccdbcc9b Update index.html 2024-04-12 21:43:21 +08:00
shadowcz007 50063903f9 mixlab app add 3D 2024-04-12 16:10:05 +08:00
shadowcz007 95a1b70533 Update app_mixlab.js 2024-04-08 17:14:30 +08:00
shadowcz007 c3679ac90b Update image_mixlab.js 2024-04-07 11:49:26 +08:00
shadowcz007 29e48eb6a2 Update ui_mixlab.js 2024-04-07 10:32:13 +08:00
7 changed files with 326 additions and 126 deletions
+17 -6
View File
@@ -1517,7 +1517,7 @@ class Image3D:
FUNCTION = "run"
CATEGORY = "♾️Mixlab/Image"
CATEGORY = "♾️Mixlab/3D"
INPUT_IS_LIST = False
OUTPUT_IS_LIST = (False,False,False,False,)
@@ -2738,6 +2738,7 @@ class ImageColorTransfer:
return {"required": {
"source": ("IMAGE",),
"target": ("IMAGE",),
"weight": ("FLOAT", {"default": 0.5, "min": 0.0, "max": 1.0, "step": 0.01}),
},
}
@@ -2756,20 +2757,30 @@ class ImageColorTransfer:
# 输出是否为列表
OUTPUT_IS_LIST = (True,)
def run(self,source,target):
def run(self,source,target,weight):
res=[]
target=target[0][0]
print(target.shape)
# print(target.shape)
target=tensor2pil(target)
for ims in source:
for im in ims:
image=tensor2pil(im)
image=color_transfer(image,target)
image=pil2tensor(image)
res.append(image)
image_res=color_transfer(image,target)
# weight Blend image # contributors:@ning
blend_mask = Image.new(mode="L", size=image.size,
color=(round(weight[0] * 255)))
blend_mask = ImageOps.invert(blend_mask)
img_result = Image.composite(image, image_res, blend_mask)
del image, image_res, blend_mask
img_result=pil2tensor(img_result)
res.append(img_result)
return (res,)
+194 -44
View File
@@ -406,6 +406,7 @@
<link href="/extensions/comfyui-mixlab-nodes/lib/classic.min.css" rel="stylesheet">
<script src="/extensions/comfyui-mixlab-nodes/lib/pickr.min.js"></script>
<script src="/extensions/comfyui-mixlab-nodes/lib/filerobot-image-editor.min.js"></script>
<script type="module" src="/extensions/comfyui-mixlab-nodes/lib/model-viewer.min.js"></script>
<link rel="stylesheet" href="/extensions/comfyui-mixlab-nodes/lib/login.css">
</head>
@@ -498,6 +499,39 @@
})
}
//给load image to batch节点使用的输入
function createBase64ImageForLoadImageToBatch(imageElement, nodeId, bs) {
let im = new Image()
im.src = bs;
im.className = "base64"
imageElement.appendChild(im);
let base64s = imageElement.querySelectorAll('.base64')
//更新输入
window._appData.data[nodeId].inputs.images.base64 = Array.from(base64s, (b) => b.src)
// 删除
im.addEventListener('click', e => {
e.preventDefault();
im.remove();
let base64s = imageElement.querySelectorAll('.base64')
//更新输入
window._appData.data[nodeId].inputs.images.base64 = Array.from(base64s, (b) => b.src)
})
}
const blobToBase64 = blob => {
return new Promise((res, rej) => {
const reader = new FileReader()
reader.onloadend = () => {
const base64data = reader.result
res(base64data)
// 在这里可以将base64数据用于进一步处理或显示图片
}
reader.readAsDataURL(blob)
})
}
function base64ToBlob(base64) {
// 去除base64编码中的前缀
@@ -712,7 +746,7 @@
data[id].inputs.noise_seed = Math.round(Math.random() * max_seed)
}
// class_type:"Seed_"
if (data[id].class_type == "Seed_") {
if (data[id].class_type == "Seed_" && ['increment', 'decrement', 'randomize'].includes(seed[id])) {
data[id].inputs.seed = Math.round(Math.random() * max_seed)
}
console.log('new Seed', data[id])
@@ -912,6 +946,9 @@
// copyImagesToClipboard(output_card.outerHTML)
})
//是否显示复制图片,复制html两个按钮
let isShowImageFn = false;
for (const node of outputData) {
// console.log('output', node)
if (node.class_type == "ShowTextForGPT") {
@@ -930,7 +967,11 @@
output_card.appendChild(div);
};
if (["SaveImage", "PreviewImage", "PromptImage", "Image Save", "SaveImageAndMetadata_"].includes(node.class_type)) {
if (["SaveImage",
"PreviewImage",
"PromptImage",
"Image Save",
"SaveImageAndMetadata_"].includes(node.class_type)) {
let a = document.createElement('a');
a.id = `output_${node.id}`
@@ -939,6 +980,23 @@
a.setAttribute('target', "_blank");
a.setAttribute('href', base64Df);
let img = new Image();
// img;
img.src = base64Df;
a.appendChild(img)
output_card.appendChild(a);
isShowImageFn = true;
}
//3d
if (["SaveTripoSRMesh"].includes(node.class_type)) {
let a = document.createElement('a');
a.id = `output_${node.id}`
a.setAttribute('data-pswp-width', "200");
a.setAttribute('data-pswp-height', "200");
a.setAttribute('target', "_blank");
a.setAttribute('href', base64Df);
let img = new Image();
// img;
img.src = base64Df;
@@ -947,7 +1005,7 @@
}
// video ,gif
if (["VHS_VideoCombine"].includes(node.class_type)) {
if (["VHS_VideoCombine", "VideoCombine_Adv"].includes(node.class_type)) {
let a = document.createElement('a');
a.id = `output_${node.id}`
@@ -974,6 +1032,12 @@
}
}
if (isShowImageFn === false) {
copyImage.remove();
copyHTML.remove();
}
return container
}
@@ -1030,6 +1094,7 @@
}
async function handleClipboardImage(imageElement, data) {
//data.class_type === 'LoadImagesToBatch'
try {
const clipboardItems = await navigator.clipboard.read();
for (const clipboardItem of clipboardItems) {
@@ -1042,18 +1107,17 @@
if (hashId == window._appData.data[data.id].hashId) return
let { url, name } = await uploadImage(fileBlob);
// 在这里可以对 Blob 对象进行进一步处理
imageElement.src = url;
window._appData.data[data.id].inputs.image = name;
window._appData.data[data.id].hashId = hashId;
console.log("上传的文件:", url, data.id, name);
// const img = document.createElement('img');
// img.src = URL.createObjectURL(blob);
// document.body.appendChild(img);
// console.log( URL.createObjectURL(blob));
if (data.class_type === 'LoadImagesToBatch') {
let base64 = await blobToBase64(fileBlob)
createBase64ImageForLoadImageToBatch(imageElement, data.id, base64)
} else {
let { url, name } = await uploadImage(fileBlob);
// 在这里可以对 Blob 对象进行进一步处理
imageElement.src = url;
window._appData.data[data.id].inputs.image = name;
window._appData.data[data.id].hashId = hashId;
console.log("上传的文件:", url, data.id, name);
}
}
}
}
@@ -1217,10 +1281,16 @@
console.log('inputData', data);
// 图片 or 视频输入
if (data.class_type === "LoadImage" || data.class_type === "VHS_LoadVideo" || data.class_type === 'ImagesPrompt_') {
if (["LoadImage",
"VHS_LoadVideo",
"ImagesPrompt_",
"LoadImagesToBatch"].includes(data.class_type)) {
let isVideoUpload = data.class_type === "VHS_LoadVideo";
let isBase64Upload = data.class_type === "LoadImagesToBatch";
// Create a container for the upload control
const uploadContainer = document.createElement("div");
uploadContainer.className = 'card';
@@ -1252,7 +1322,7 @@
const btnForImageEdit = document.createElement("button");
btnForImageEdit.style = ` width: 32px; background: none;margin-left: 18px;`
btnForImageEdit.innerHTML = '<?xml version="1.0" ?><svg version="1.1" style="width: 24px;" viewBox="0 0 50 50" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g id="Layer_1_1_"><path d="M18.293,31.707h6.414l24-24l-6.414-6.414l-24,24V31.707z M45.879,7.707l-3.586,3.586l-3.586-3.586l3.586-3.586 L45.879,7.707z M20.293,26.121l17-17l3.586,3.586l-17,17h-3.586V26.121z"/><polygon points="43.293,19.707 41.293,19.707 41.293,46.707 3.293,46.707 3.293,8.707 31.293,8.707 31.293,6.707 1.293,6.707 1.293,48.707 43.293,48.707 "/></g></svg>'
if (!isVideoUpload && data.class_type !== 'ImagesPrompt_') actionDiv.appendChild(btnForImageEdit);
if ((!isVideoUpload && !isBase64Upload) && data.class_type !== 'ImagesPrompt_') actionDiv.appendChild(btnForImageEdit);
uploadContainer.appendChild(actionDiv)
@@ -1299,6 +1369,16 @@
})
uploadContainer.appendChild(imgDiv);
window._appData.data[data.id].inputs.image_base64 = mainImage.querySelector('.images_prompt_main').src;
} else if (data.class_type === 'LoadImagesToBatch') {
// 多张base64 图片
let base64 = data.inputs.images.base64
imageElement = document.createElement('div');
imageElement.className = "images"
for (const bs of base64) {
createBase64ImageForLoadImageToBatch(imageElement, data.id, bs)
}
}
@@ -1306,7 +1386,7 @@
if (!isVideoUpload) btnFromClipboard.addEventListener('click', (event) => handleClipboardImage(imageElement, data));
if (!isVideoUpload) btnForImageEdit.addEventListener('click', e => editImage(imageElement, data))
if (!isVideoUpload && !isBase64Upload) btnForImageEdit.addEventListener('click', e => editImage(imageElement, data))
uploadImageInput.addEventListener('click', (event) => {
@@ -1329,33 +1409,43 @@
if (hashId == window._appData.data[data.id].hashId) return
let { url, name } = await uploadImage(fileBlob, '.' + file.type.split('/')[1])
if (data.class_type === 'ImagesPrompt_') {
//
let base64 = await parseImageToBase64(url);
uploadContainer.querySelector('.images_prompt_main').src = base64
window._appData.data[data.id].inputs.image_base64 = base64;
if (data.class_type === 'LoadImagesToBatch') {
// 上传 ,转为base64
let base64 = await blobToBase64(fileBlob)
createBase64ImageForLoadImageToBatch(imageElement, data.id, base64)
} else {
if (isVideoUpload) {
imageElement.srcObject = null;
}
// 在这里可以对 Blob 对象进行进一步处理
imageElement.src = url;
//上传,返回url
let { url, name } = await uploadImage(fileBlob, '.' + file.type.split('/')[1])
if (isVideoUpload) {
window._appData.data[data.id].inputs.video = name;
if (data.class_type === 'ImagesPrompt_') {
//
let base64 = await parseImageToBase64(url);
uploadContainer.querySelector('.images_prompt_main').src = base64
window._appData.data[data.id].inputs.image_base64 = base64;
} else {
window._appData.data[data.id].inputs.image = name;
if (isVideoUpload) {
imageElement.srcObject = null;
}
// 在这里可以对 Blob 对象进行进一步处理
imageElement.src = url;
if (isVideoUpload) {
window._appData.data[data.id].inputs.video = name;
} else {
window._appData.data[data.id].inputs.image = name;
}
}
window._appData.data[data.id].hashId = hashId;
console.log("上传的文件:", url, data.id, name);
}
window._appData.data[data.id].hashId = hashId;
console.log("上传的文件:", url, data.id, name);
};
// 开始读取文件
@@ -2201,6 +2291,51 @@
if (val && type == "text" && output.querySelector(`#output_${id}`)) output.querySelector(`#output_${id}`).innerText = val;
// 3d meshes
if (val && type == 'meshes' && output.querySelector(`#output_${id}`)) {
let threeD = output.querySelector('.threeD')
//判断默认的图片,需要去掉后创建model-viewer
let imgDf = output.querySelector(`#output_${id} img`);
if (!threeD) {
if (imgDf) imgDf.parentElement.remove();
threeD = document.createElement('div');
threeD.className = 'threeD'
threeD.id = `output_${id}`
output.querySelector('.output_card').appendChild(threeD)
// output.insertBefore(threeD, output.firstChild);
};
for (const meshUrl of val) {
const modelViewer = document.createElement('div');
modelViewer.style = `width:300px;margin:4px;height:300px;display:block`
modelViewer.innerHTML = `<model-viewer src="${meshUrl}"
min-field-of-view="0deg" max-field-of-view="180deg"
shadow-intensity="1"
camera-controls
touch-action="pan-y"
style="width:300px;height:300px;"
>
<div class="controls">
<button class="export">Save As</button>
</div>
</model-viewer>`
const btn = modelViewer.querySelector('.export');
btn.addEventListener('click', async e => {
e.preventDefault();
const glTF = await (modelViewer.querySelector('model-viewer')).exportScene()
const file = new File([glTF], 'mixlab.glb')
const link = document.createElement('a')
link.download = file.name
link.href = URL.createObjectURL(file)
link.click()
})
threeD.appendChild(modelViewer)
}
}
}
},
submitButton: {
@@ -2316,6 +2451,9 @@
const _images = detail?.output?._images;
const prompts = detail?.output?.prompts;
// 3d模型
const meshes = detail?.output?.mesh;
if (images) {
// if (!images) return;
@@ -2325,6 +2463,14 @@
return `${url}/view?filename=${encodeURIComponent(img.filename)}&type=${img.type}&subfolder=${encodeURIComponent(img.subfolder)}&t=${+new Date()}`;
}), detail.node, 'images');
} else if (meshes) {
//多个
let url = get_url();
show(Array.from(meshes, mesh => {
return `${url}/view?filename=${encodeURIComponent(mesh.filename)}&type=${mesh.type}&subfolder=${encodeURIComponent(mesh.subfolder)}&t=${+new Date()}`;
}), detail.node, 'meshes');
} else if (_images && prompts) {
let url = get_url();
@@ -2558,6 +2704,9 @@
// 创建app的选择菜单
function createAppList(apps = [], innerApp = false) {
window.prompt_ids = {};
if (document.body.querySelector('.apps')) {
document.body.querySelector('.apps').remove()
}
let details = document.createElement('details');
details.className = 'apps';
@@ -2758,20 +2907,21 @@
card.addEventListener('click', async e => {
e.preventDefault();
// console.log(c)
const { category, filename } = c.appInfo;
window._appData = (await get_my_app(category, filename))[0];
await createApp(window._appData);
executed(c.data, window._show);
try {
document.body.querySelector('#app_container').setAttribute('open', true)
document.body.querySelector('#app_input_pannel').removeAttribute('open')
document.body.querySelector('.apps').removeAttribute('open')
} catch (error) {
console.log(error)
}
const { category, filename } = c.appInfo;
window._appData = (await get_my_app(category, filename))[0];
createApp(window._appData);
executed(c.data, window._show);
})
}
+2 -1
View File
@@ -197,7 +197,8 @@ async function extractInputAndOutputData (
if (
node.type === 'KSampler' ||
node.type == 'SamplerCustom' ||
node.type === 'ChinesePrompt_Mix'
node.type === 'ChinesePrompt_Mix' ||
node.type === 'Seed_'
) {
// seed 的类型收集
try {
+1 -1
View File
@@ -3,7 +3,7 @@ import { app } from '../../../scripts/app.js'
const repoOwner = 'shadowcz007' // 替换为仓库的所有者
const repoName = 'comfyui-mixlab-nodes' // 替换为仓库的名称
const version = 'v0.20.0'
const version = 'v0.21.0'
fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
.then(response => response.json())
+29 -8
View File
@@ -632,11 +632,11 @@ const createInputImageForBatch = (base64, widget) => {
im.addEventListener('click', e => {
let newValue = []
let items=widget.value?.base64||[];
let items = widget.value?.base64 || []
for (const v of items) {
if (v != base64) newValue.push(v)
}
widget.value.base64=newValue;
widget.value.base64 = newValue
im.remove()
})
@@ -651,7 +651,7 @@ app.registerExtension({
// console.log('##node', node)
const widget = {
value: {
base64:[]
base64: []
}, // 不能[x,x,x]
type: inputData[0], // the type
name: inputName, // the name, slice
@@ -659,7 +659,7 @@ app.registerExtension({
draw (ctx, node, width, y) {},
computeSize (...args) {
return [128, 32] // a method to compute the current size of the widget
},
}
// serializeValue (nodeId, widgetIndex) {
// return widget.value
// },
@@ -688,7 +688,7 @@ app.registerExtension({
get_position_style(ctx, widget_width, 44, node.size[1])
)
},
serialize:false
serialize: false
}
widget.div = $el('div', {})
@@ -703,8 +703,10 @@ app.registerExtension({
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]
@@ -712,16 +714,35 @@ app.registerExtension({
reader.onload = async event => {
const base64 = event.target.result
// console.log(base64)
if (!imagesWidget.value) imagesWidget.value = {base64:[]}
if (!imagesWidget.value) imagesWidget.value = { base64: [] }
imagesWidget.value.base64.push(base64)
let im = createInputImageForBatch(base64,imagesWidget)
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)
@@ -753,7 +774,7 @@ app.registerExtension({
let pre = imagePreview.div.querySelector('.images_preview')
for (const d of imagesWidget.value?.base64 || []) {
let im = createInputImageForBatch(d,imagesWidget)
let im = createInputImageForBatch(d, imagesWidget)
pre.appendChild(im)
}
}
+2 -2
View File
@@ -1511,14 +1511,14 @@ app.registerExtension({
document.body.appendChild(div)
}
},
{
apps_opts.length>0?{
content: 'Workflow App ♾️Mixlab',
has_submenu: true,
disabled: false,
submenu: {
options: apps_opts
}
}
}:null
)
return options
+81 -64
View File
File diff suppressed because one or more lines are too long