Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fee0222910 | ||
|
|
1ed7b5511f | ||
|
|
b8f7c31537 | ||
|
|
164791c257 | ||
|
|
8f5e599928 | ||
|
|
7a7aaeb84d | ||
|
|
e2136ab2fc | ||
|
|
c75cb21946 | ||
|
|
bf95218c91 | ||
|
|
8cb4507a5f | ||
|
|
555890d1ba | ||
|
|
e4f54e83b6 | ||
|
|
692c4a709e | ||
|
|
cbd1961459 | ||
|
|
2e31a33ebf | ||
|
|
d16c6137d2 | ||
|
|
0416ab79ec | ||
|
|
fc9a1c62b9 | ||
|
|
ae4a17d271 | ||
|
|
8e488d4b1d |
@@ -5,6 +5,11 @@
|
||||
|
||||
|
||||
##### `最新`:
|
||||
- 移动端适配、修改app模式的Mask编辑器
|
||||
|
||||
- 增加p5.js作为输入节点
|
||||
[workflow](./workflow/p5workflow.json)
|
||||
[workflow2](./workflow/p5-video-workflow.json)
|
||||
|
||||
- App模式增加batch prompt,批量提示词,可以把动态提示词批量组成后运行
|
||||
|
||||
@@ -14,10 +19,6 @@
|
||||
|
||||
- 增加 SiliconflowLLM,可以使用由Siliconflow提供的免费LLM
|
||||
|
||||
- 增加 Edit Mask,方便在生成的时候手动绘制 mask [workflow](./workflow/edit-mask-workflow.json)
|
||||
|
||||
- LaMaInpainting 调整为手动安装
|
||||
|
||||
<!-- - ChatGPT 节点支持 Local LLM(llama.cpp),Phi3、llama3 都可以直接一个节点运行了。模型下载后,放置到 `models/llamafile/` -->
|
||||
|
||||
<!-- - 右键菜单支持 text-to-text,方便对 prompt 词补全 -->
|
||||
@@ -246,6 +247,8 @@ pip install llama-cpp-python \
|
||||
|
||||
### Other Nodes
|
||||
|
||||
- 增加 Edit Mask,方便在生成的时候手动绘制 mask [workflow](./workflow/edit-mask-workflow.json)
|
||||
|
||||

|
||||

|
||||
|
||||
|
||||
+10
-5
@@ -782,7 +782,7 @@ def random_seed(seed, data):
|
||||
@routes.post("/mixlab/prompt")
|
||||
async def mixlab_post_prompt(request):
|
||||
p_intance=PromptServer.instance
|
||||
logging.info("got prompt")
|
||||
logging.info("/mixlab/prompt")
|
||||
resp_code = 200
|
||||
out_string = ""
|
||||
json_data = await request.json()
|
||||
@@ -795,13 +795,17 @@ async def mixlab_post_prompt(request):
|
||||
# 种子
|
||||
seed=json_data['seed'] if "seed" in json_data else {}
|
||||
|
||||
apps=get_my_workflow_for_app(json_data['filename'],json_data['category'],False)
|
||||
try:
|
||||
apps=json_data['apps']
|
||||
except:
|
||||
apps=get_my_workflow_for_app(json_data['filename'],json_data['category'],False)
|
||||
|
||||
prompt=json_data['prompt'] if 'prompt' in json_data else None
|
||||
|
||||
if len(apps)==1:
|
||||
|
||||
if len(apps)>0:
|
||||
# 取到prompt
|
||||
prompt=apps[0]['data']['output']
|
||||
# logging.info(prompt)
|
||||
# 更新input_data到prompt里
|
||||
'''
|
||||
{
|
||||
@@ -1009,7 +1013,7 @@ def mix_status(request):
|
||||
|
||||
# 导入节点
|
||||
from .nodes.PromptNode import GLIGENTextBoxApply_Advanced,EmbeddingPrompt,RandomPrompt,PromptSlide,PromptSimplification,PromptImage,JoinWithDelimiter
|
||||
from .nodes.ImageNode import ImageBatchToList_,ImageListToBatch_,ComparingTwoFrames,LoadImages_,CompositeImages,GridDisplayAndSave,GridInput,ImagesPrompt,SaveImageAndMetadata,SaveImageToLocal,SplitImage,GridOutput,GetImageSize_,MirroredImage,ImageColorTransfer,NoiseImage,TransparentImage,GradientImage,LoadImagesFromPath,LoadImagesFromURL,ResizeImage,TextImage,SvgImage,Image3D,ShowLayer,NewLayer,MergeLayers,CenterImage,AreaToMask,SmoothMask,SplitLongMask,ImageCropByAlpha,EnhanceImage,FaceToMask
|
||||
from .nodes.ImageNode import DepthViewer_,ImageBatchToList_,ImageListToBatch_,ComparingTwoFrames,LoadImages_,CompositeImages,GridDisplayAndSave,GridInput,ImagesPrompt,SaveImageAndMetadata,SaveImageToLocal,SplitImage,GridOutput,GetImageSize_,MirroredImage,ImageColorTransfer,NoiseImage,TransparentImage,GradientImage,LoadImagesFromPath,LoadImagesFromURL,ResizeImage,TextImage,SvgImage,Image3D,ShowLayer,NewLayer,MergeLayers,CenterImage,AreaToMask,SmoothMask,SplitLongMask,ImageCropByAlpha,EnhanceImage,FaceToMask
|
||||
# from .nodes.Vae import VAELoader,VAEDecode
|
||||
from .nodes.ScreenShareNode import ScreenShareNode,FloatingVideo
|
||||
|
||||
@@ -1053,6 +1057,7 @@ NODE_CLASS_MAPPINGS = {
|
||||
"ImageListToBatch_":ImageListToBatch_,
|
||||
"ImageBatchToList_":ImageBatchToList_,
|
||||
"CompositeImages_":CompositeImages,
|
||||
"DepthViewer": DepthViewer_,
|
||||
"SplitImage":SplitImage,
|
||||
"CenterImage":CenterImage,
|
||||
"GridOutput":GridOutput,
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
+4060
-449
File diff suppressed because it is too large
Load Diff
+10
-4
@@ -694,18 +694,20 @@ class JsonRepair:
|
||||
return {
|
||||
"required": {
|
||||
"json_string":("STRING", {"forceInput": True,}),
|
||||
"key":("STRING", {"multiline": False,"dynamicPrompts": False,"default": ""}),
|
||||
}
|
||||
}
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
RETURN_TYPES = ("STRING",)
|
||||
RETURN_TYPES = ("STRING","STRING",)
|
||||
RETURN_NAMES = ("json_string","value",)
|
||||
FUNCTION = "run"
|
||||
# OUTPUT_NODE = True
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
OUTPUT_IS_LIST = (False,False,)
|
||||
|
||||
CATEGORY = "♾️Mixlab/GPT"
|
||||
|
||||
def run(self, json_string):
|
||||
def run(self, json_string,key=""):
|
||||
|
||||
json_string=extract_json_strings(json_string)
|
||||
# print(json_string)
|
||||
@@ -714,7 +716,11 @@ class JsonRepair:
|
||||
# 将 JSON 字符串解析为 Python 对象
|
||||
data = json.loads(good_json_string)
|
||||
|
||||
v=""
|
||||
if key!="" and (key in data):
|
||||
v=data[key]
|
||||
|
||||
# 将 Python 对象转换回 JSON 字符串,确保中文字符不被转义
|
||||
json_str_with_chinese = json.dumps(data, ensure_ascii=False)
|
||||
|
||||
return (json_str_with_chinese,)
|
||||
return (json_str_with_chinese,v,)
|
||||
+179
-16
@@ -8,6 +8,7 @@ from PIL.PngImagePlugin import PngInfo
|
||||
import base64,os,random
|
||||
from io import BytesIO
|
||||
import folder_paths
|
||||
import node_helpers
|
||||
import json,io
|
||||
import comfy.utils
|
||||
from comfy.cli_args import args
|
||||
@@ -491,6 +492,53 @@ def load_image(fp,white_bg=False):
|
||||
|
||||
return images
|
||||
|
||||
|
||||
# 读取图片数据,转成tensor
|
||||
def load_image_to_tensor( image):
|
||||
image_path = folder_paths.get_annotated_filepath(image)
|
||||
|
||||
img = node_helpers.pillow(Image.open, image_path)
|
||||
|
||||
output_images = []
|
||||
output_masks = []
|
||||
w, h = None, None
|
||||
|
||||
excluded_formats = ['MPO']
|
||||
|
||||
for i in ImageSequence.Iterator(img):
|
||||
i = node_helpers.pillow(ImageOps.exif_transpose, i)
|
||||
|
||||
if i.mode == 'I':
|
||||
i = i.point(lambda i: i * (1 / 255))
|
||||
image = i.convert("RGB")
|
||||
|
||||
if len(output_images) == 0:
|
||||
w = image.size[0]
|
||||
h = image.size[1]
|
||||
|
||||
if image.size[0] != w or image.size[1] != h:
|
||||
continue
|
||||
|
||||
image = np.array(image).astype(np.float32) / 255.0
|
||||
image = torch.from_numpy(image)[None,]
|
||||
if 'A' in i.getbands():
|
||||
mask = np.array(i.getchannel('A')).astype(np.float32) / 255.0
|
||||
mask = 1. - torch.from_numpy(mask)
|
||||
else:
|
||||
mask = torch.zeros((64,64), dtype=torch.float32, device="cpu")
|
||||
output_images.append(image)
|
||||
output_masks.append(mask.unsqueeze(0))
|
||||
|
||||
if len(output_images) > 1 and img.format not in excluded_formats:
|
||||
output_image = torch.cat(output_images, dim=0)
|
||||
output_mask = torch.cat(output_masks, dim=0)
|
||||
else:
|
||||
output_image = output_images[0]
|
||||
output_mask = output_masks[0]
|
||||
|
||||
return (output_image, output_mask)
|
||||
|
||||
|
||||
def load_image_and_mask_from_url(url, timeout=10):
|
||||
# Load the image from the URL
|
||||
response = requests.get(url, timeout=timeout)
|
||||
@@ -1687,28 +1735,51 @@ class Image3D:
|
||||
def run(self,upload,material=None):
|
||||
# print('material',material)
|
||||
# print(upload )
|
||||
image = base64_to_image(upload['image'])
|
||||
|
||||
mat=None
|
||||
if 'material' in upload and upload['material']:
|
||||
mat=base64_to_image(upload['material'])
|
||||
mat=mat.convert('RGB')
|
||||
mat=pil2tensor(mat)
|
||||
# 截取的系列角度截图
|
||||
images=upload['images'] if "images" in upload else []
|
||||
|
||||
mask = image.split()[3]
|
||||
image=image.convert('RGB')
|
||||
ims=[]
|
||||
for im in images:
|
||||
if 'type' in im and (not f"[{im['type']}]" in im['name']):
|
||||
im['name']=im['name']+" "+f"[{im['type']}]"
|
||||
output_image, output_mask = load_image_to_tensor(im['name'])
|
||||
ims.append(output_image)
|
||||
|
||||
mask=mask.convert('L')
|
||||
|
||||
|
||||
mask=None
|
||||
bg_image=None
|
||||
if 'bg_image' in upload and upload['bg_image']:
|
||||
bg_image = base64_to_image(upload['bg_image'])
|
||||
bg_image=bg_image.convert('RGB')
|
||||
bg_image=pil2tensor(bg_image)
|
||||
mat=None
|
||||
|
||||
# 如果没有系列截图
|
||||
if len(ims)==0:
|
||||
# 这个是3d模型当前截图
|
||||
image = base64_to_image(upload['image'])
|
||||
|
||||
|
||||
if 'material' in upload and upload['material']:
|
||||
mat=base64_to_image(upload['material'])
|
||||
mat=mat.convert('RGB')
|
||||
mat=pil2tensor(mat)
|
||||
|
||||
mask = image.split()[3]
|
||||
image=image.convert('RGB')
|
||||
|
||||
mask=mask.convert('L')
|
||||
|
||||
|
||||
if 'bg_image' in upload and 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)
|
||||
mask=pil2tensor(mask)
|
||||
image=pil2tensor(image)
|
||||
else:
|
||||
|
||||
image = torch.cat(ims, dim=0)
|
||||
|
||||
|
||||
m=[]
|
||||
if not material is None:
|
||||
@@ -3220,3 +3291,95 @@ class ImageListToBatch_:
|
||||
out = torch.cat(out, dim=0)
|
||||
|
||||
return (out,)
|
||||
|
||||
|
||||
# https://github.com/gokayfem/ComfyUI-Depth-Visualization?tab=readme-ov-file
|
||||
class DepthViewer_:
|
||||
@classmethod
|
||||
def INPUT_TYPES(cls):
|
||||
return {
|
||||
"required": {
|
||||
"image": ("IMAGE",),
|
||||
"depth_map": ("IMAGE",),
|
||||
|
||||
},
|
||||
"optional":{
|
||||
"frames":("IMAGEBASE64",),
|
||||
},
|
||||
}
|
||||
|
||||
def __init__(self):
|
||||
self.saved_reference = []
|
||||
self.saved_depth = []
|
||||
|
||||
self.full_output_folder,self.filename,self.counter, self.subfolder, self.filename_prefix = folder_paths.get_save_image_path(
|
||||
"imagesave",
|
||||
folder_paths.get_output_directory())
|
||||
|
||||
|
||||
RETURN_TYPES = ("IMAGE",)
|
||||
RETURN_NAMES = ("frames",)
|
||||
|
||||
OUTPUT_NODE = True
|
||||
|
||||
INPUT_IS_LIST = False
|
||||
OUTPUT_IS_LIST = (False,)
|
||||
|
||||
FUNCTION = "run"
|
||||
CATEGORY = "♾️Mixlab/3D"
|
||||
def run(self, image, depth_map,frames=None):
|
||||
self.saved_reference.clear()
|
||||
self.saved_depth.clear()
|
||||
image = image[0].detach().cpu().numpy()
|
||||
depth = depth_map[0].detach().cpu().numpy()
|
||||
|
||||
image = Image.fromarray(np.clip(255. * image, 0, 255).astype(np.uint8)).convert('RGB')
|
||||
depth = Image.fromarray(np.clip(255. * depth, 0, 255).astype(np.uint8))
|
||||
|
||||
return self.display([image], [depth],frames)
|
||||
|
||||
def display(self, reference_image, depth_map,frames):
|
||||
for (batch_number, (single_image, single_depth)) in enumerate(zip(reference_image, depth_map)):
|
||||
filename_with_batch_num = self.filename.replace("%batch_num%", str(batch_number))
|
||||
|
||||
image_file = f"{filename_with_batch_num}_{self.counter:05}_reference.png"
|
||||
single_image.save(os.path.join(self.full_output_folder, image_file))
|
||||
|
||||
depth_file = f"{filename_with_batch_num}_{self.counter:05}_depth.png"
|
||||
single_depth.save(os.path.join(self.full_output_folder, depth_file))
|
||||
|
||||
self.saved_reference.append({
|
||||
"filename": image_file,
|
||||
"subfolder": self.subfolder,
|
||||
"type": "output"
|
||||
})
|
||||
|
||||
self.saved_depth.append({
|
||||
"filename": depth_file,
|
||||
"subfolder": self.subfolder,
|
||||
"type": "output"
|
||||
})
|
||||
self.counter += 1
|
||||
|
||||
|
||||
ims=[]
|
||||
image1 = Image.new('RGB', (512, 512), color='black')
|
||||
image1=pil2tensor(image1)
|
||||
|
||||
if frames!=None:
|
||||
for im in frames['images']:
|
||||
# print(im)
|
||||
if 'type' in im and (not f"[{im['type']}]" in im['name']):
|
||||
im['name']=im['name']+" "+f"[{im['type']}]"
|
||||
|
||||
output_image, output_mask = load_image_to_tensor(im['name'])
|
||||
ims.append(output_image)
|
||||
|
||||
if len(ims)>0:
|
||||
image1 = ims[0]
|
||||
for image2 in ims[1:]:
|
||||
if image1.shape[1:] != image2.shape[1:]:
|
||||
image2 = comfy.utils.common_upscale(image2.movedim(-1, 1), image1.shape[2], image1.shape[1], "bilinear", "center").movedim(1, -1)
|
||||
image1 = torch.cat((image1, image2), dim=0)
|
||||
|
||||
return {"ui": {"reference_image": self.saved_reference, "depth_map": self.saved_depth}, "result": (image1,)}
|
||||
+2
-2
@@ -16,7 +16,7 @@ def tensor2pil(image):
|
||||
def pil2tensor(image):
|
||||
return torch.from_numpy(np.array(image).astype(np.float32) / 255.0).unsqueeze(0)
|
||||
|
||||
def load_image( image):
|
||||
def load_image_to_tensor( image):
|
||||
image_path = folder_paths.get_annotated_filepath(image)
|
||||
|
||||
img = node_helpers.pillow(Image.open, image_path)
|
||||
@@ -88,7 +88,7 @@ class P5Input:
|
||||
if 'type' in im and (not f"[{im['type']}]" in im['name']):
|
||||
im['name']=im['name']+" "+f"[{im['type']}]"
|
||||
|
||||
output_image, output_mask = load_image(im['name'])
|
||||
output_image, output_mask = load_image_to_tensor(im['name'])
|
||||
ims.append(output_image)
|
||||
|
||||
if len(ims)==0:
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
[project]
|
||||
name = "comfyui-mixlab-nodes"
|
||||
description = "3D, ScreenShareNode & FloatingVideoNode, SpeechRecognition & SpeechSynthesis, GPT, LoadImagesFromLocal, Layers, Other Nodes, ..."
|
||||
version = "0.35.2"
|
||||
version = "0.37.0"
|
||||
license = "MIT"
|
||||
dependencies = ["numpy", "pyOpenSSL", "watchdog", "opencv-python-headless", "matplotlib", "openai", "simple-lama-inpainting", "clip-interrogator==0.6.0", "transformers>=4.36.0", "lark-parser", "imageio-ffmpeg", "rembg[gpu]", "omegaconf==2.3.0", "Pillow>=9.5.0", "einops==0.7.0", "trimesh>=4.0.5", "huggingface-hub", "scikit-image"]
|
||||
|
||||
|
||||
+937
-507
File diff suppressed because it is too large
Load Diff
+344
-236
@@ -26,7 +26,8 @@ const setLocalDataOfWin = (key, value) => {
|
||||
localStorage.setItem(key, JSON.stringify(value))
|
||||
// window[key] = value
|
||||
}
|
||||
async function uploadImage (blob, fileType = '.svg', filename) {
|
||||
|
||||
async function uploadImage_ (blob, fileType = '.svg', filename) {
|
||||
// const blob = await (await fetch(src)).blob();
|
||||
const body = new FormData()
|
||||
body.append(
|
||||
@@ -41,13 +42,17 @@ async function uploadImage (blob, fileType = '.svg', filename) {
|
||||
|
||||
// console.log(resp)
|
||||
let data = await resp.json()
|
||||
return data
|
||||
}
|
||||
|
||||
async function uploadImage (blob, fileType = '.svg', filename) {
|
||||
let data = await uploadImage_(blob, fileType, filename)
|
||||
let { name, subfolder } = data
|
||||
let src = api.apiURL(
|
||||
`/view?filename=${encodeURIComponent(
|
||||
name
|
||||
)}&type=input&subfolder=${subfolder}${app.getPreviewFormatParam()}${app.getRandParam()}`
|
||||
)
|
||||
|
||||
return src
|
||||
}
|
||||
|
||||
@@ -171,6 +176,42 @@ async function changeMaterial (
|
||||
targetMaterial.pbrMetallicRoughness.baseColorTexture.setTexture(targetTexture)
|
||||
}
|
||||
|
||||
function inputFileClick (isFileURL = false, isGlb = false) {
|
||||
return new Promise((res, rej) => {
|
||||
// 创建一个input元素
|
||||
var input = document.createElement('input')
|
||||
input.type = 'file'
|
||||
input.accept = isGlb ? '.glb' : 'image/*'
|
||||
|
||||
// 监听input的change事件
|
||||
input.addEventListener('change', function () {
|
||||
// 获取上传的文件
|
||||
var file = input.files[0]
|
||||
|
||||
if (isFileURL) {
|
||||
res(URL.createObjectURL(file))
|
||||
return
|
||||
}
|
||||
|
||||
// 创建一个FileReader对象来读取文件
|
||||
var reader = new FileReader()
|
||||
|
||||
// 监听FileReader的load事件
|
||||
reader.addEventListener('load', async () => {
|
||||
let base64 = reader.result
|
||||
input.remove()
|
||||
res(base64)
|
||||
})
|
||||
|
||||
// 读取文件
|
||||
reader.readAsDataURL(file)
|
||||
})
|
||||
|
||||
// 触发input的点击事件
|
||||
input.click()
|
||||
})
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.3D.3DImage',
|
||||
async getCustomWidgets (app) {
|
||||
@@ -189,7 +230,7 @@ app.registerExtension({
|
||||
let d = getLocalData('_mixlab_3d_image')
|
||||
// console.log('serializeValue', node)
|
||||
if (d && d[node.id]) {
|
||||
let { url, bg, material } = d[node.id]
|
||||
let { url, bg, material, images } = d[node.id]
|
||||
let data = {}
|
||||
if (url) {
|
||||
data.image = await parseImage(url)
|
||||
@@ -205,6 +246,10 @@ app.registerExtension({
|
||||
data.material = await parseImage(material)
|
||||
}
|
||||
|
||||
if (images) {
|
||||
data.images = images
|
||||
}
|
||||
|
||||
return JSON.parse(JSON.stringify(data))
|
||||
} else {
|
||||
return {}
|
||||
@@ -243,39 +288,29 @@ app.registerExtension({
|
||||
|
||||
const inputDiv = (key, placeholder, preview) => {
|
||||
let div = document.createElement('div')
|
||||
const ip = document.createElement('input')
|
||||
ip.type = 'file'
|
||||
const ip = document.createElement('button')
|
||||
ip.className = `${'comfy-multiline-input'} ${placeholder}`
|
||||
div.style = `display: flex;
|
||||
align-items: center;
|
||||
margin: 6px 8px;
|
||||
margin-top: 0;`
|
||||
ip.placeholder = placeholder
|
||||
// ip.value = value
|
||||
|
||||
ip.style = `outline: none;
|
||||
border: none;
|
||||
padding: 4px;
|
||||
width: 60%;cursor: pointer;
|
||||
width: 100px;cursor: pointer;
|
||||
height: 32px;`
|
||||
const label = document.createElement('label')
|
||||
label.style = 'font-size: 10px;min-width:32px'
|
||||
label.innerText = placeholder
|
||||
div.appendChild(label)
|
||||
ip.innerText = placeholder
|
||||
div.appendChild(ip)
|
||||
|
||||
let that = this,
|
||||
filename = new Date().getTime()
|
||||
let that = this
|
||||
|
||||
ip.addEventListener('change', async 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 src="${fileURL}"
|
||||
ip.addEventListener('click', async event => {
|
||||
let fileURL = await inputFileClick(true, true)
|
||||
|
||||
// console.log('文件URL: ', fileURL)
|
||||
let html = `<model-viewer src="${fileURL}"
|
||||
oncontextmenu="return false;"
|
||||
min-field-of-view="0deg" max-field-of-view="180deg"
|
||||
shadow-intensity="1"
|
||||
camera-controls
|
||||
@@ -285,230 +320,303 @@ app.registerExtension({
|
||||
<div>Variant: <select class="variant"></select></div>
|
||||
<div>Material: <select class="material"></select></div>
|
||||
<div>Material: <div class="material_img"> </div></div>
|
||||
<div><button class="bg">BG</button></div>
|
||||
<div>
|
||||
<button class="bg">BG</button>
|
||||
|
||||
</div>
|
||||
<div>
|
||||
<input class="ddcap_step" type="number" min="1" max="20" step="1" value="1">
|
||||
<input class="total_images" type="number" min="1" max="180" step="1" value="40">
|
||||
<input class="ddcap_range" type="range" min="-180" max="180" step="1" value="0">
|
||||
<input class="ddcap_range_top" type="range" min="-180" max="180" step="1" value="0">
|
||||
<button class="ddcap">Capture Rotational Screenshots</button></div>
|
||||
|
||||
<div><button class="export">Export GLB</button></div>
|
||||
|
||||
</div></model-viewer>`
|
||||
|
||||
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 selectMaterial = preview.querySelector('.material')
|
||||
const material_img = preview.querySelector('.material_img')
|
||||
const bg = preview.querySelector('.bg')
|
||||
const exportGLB = preview.querySelector('.export')
|
||||
|
||||
if (modelViewerVariants) {
|
||||
modelViewerVariants.style.width = `${that.size[0] - 24}px`
|
||||
modelViewerVariants.style.height = `${that.size[1] - 48}px`
|
||||
}
|
||||
|
||||
modelViewerVariants.addEventListener('load', async () => {
|
||||
const names = modelViewerVariants.availableVariants
|
||||
|
||||
// 变量
|
||||
for (const name of names) {
|
||||
const option = document.createElement('option')
|
||||
option.value = name
|
||||
option.textContent = name
|
||||
select.appendChild(option)
|
||||
}
|
||||
// Adds a default option.
|
||||
if (names.length === 0) {
|
||||
const option = document.createElement('option')
|
||||
option.value = 'default'
|
||||
option.textContent = 'Default'
|
||||
select.appendChild(option)
|
||||
}
|
||||
|
||||
// 材质
|
||||
extractMaterial(
|
||||
modelViewerVariants,
|
||||
selectMaterial,
|
||||
material_img
|
||||
)
|
||||
})
|
||||
|
||||
let timer = null
|
||||
const delay = 500 // 延迟时间,单位为毫秒
|
||||
|
||||
async function checkCameraChange () {
|
||||
let dd = getLocalData(key)
|
||||
let base64Data = modelViewerVariants.toDataURL()
|
||||
|
||||
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)
|
||||
|
||||
let bg_blob = await base64ToBlobFromURL(
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mN88uXrPQAFwwK/6xJ6CQAAAABJRU5ErkJggg=='
|
||||
)
|
||||
let url_bg = await uploadImage(bg_blob, '.png')
|
||||
// console.log('url_bg',url_bg)
|
||||
|
||||
if (!dd[that.id]) {
|
||||
dd[that.id] = { url, bg: url_bg }
|
||||
} else {
|
||||
dd[that.id] = { ...dd[that.id], url }
|
||||
}
|
||||
|
||||
// 材质贴图
|
||||
let thumbUrl = material_img.getAttribute('src')
|
||||
if (thumbUrl) {
|
||||
let tb = await base64ToBlobFromURL(thumbUrl)
|
||||
let tUrl = await uploadImage(tb, '.png')
|
||||
// console.log('材质贴图', tUrl, thumbUrl)
|
||||
dd[that.id].material = tUrl
|
||||
}
|
||||
|
||||
setLocalDataOfWin(key, dd)
|
||||
}
|
||||
|
||||
function startTimer () {
|
||||
if (timer) clearTimeout(timer)
|
||||
timer = setTimeout(checkCameraChange, delay)
|
||||
}
|
||||
|
||||
modelViewerVariants.addEventListener('camera-change', startTimer)
|
||||
|
||||
select.addEventListener('input', async event => {
|
||||
modelViewerVariants.variantName =
|
||||
event.target.value === 'default' ? null : event.target.value
|
||||
// 材质
|
||||
await extractMaterial(
|
||||
modelViewerVariants,
|
||||
selectMaterial,
|
||||
material_img
|
||||
)
|
||||
checkCameraChange()
|
||||
})
|
||||
|
||||
selectMaterial.addEventListener('input', event => {
|
||||
// console.log(selectMaterial.value)
|
||||
material_img.setAttribute('src', selectMaterial.value)
|
||||
|
||||
if (selectMaterial.getAttribute('data-new-material')) {
|
||||
let index =
|
||||
~~selectMaterial.selectedOptions[0].getAttribute(
|
||||
'data-index'
|
||||
)
|
||||
changeMaterial(
|
||||
modelViewerVariants,
|
||||
modelViewerVariants.model.materials[index],
|
||||
selectMaterial.getAttribute('data-new-material')
|
||||
)
|
||||
}
|
||||
|
||||
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()
|
||||
})
|
||||
|
||||
exportGLB.addEventListener('click', async () => {
|
||||
const glTF = await modelViewerVariants.exportScene()
|
||||
const file = new File([glTF], 'export.glb')
|
||||
const link = document.createElement('a')
|
||||
link.download = file.name
|
||||
link.href = URL.createObjectURL(file)
|
||||
link.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`
|
||||
}
|
||||
}
|
||||
preview.innerHTML = html
|
||||
if (that.size[1] < 400) {
|
||||
that.setSize([that.size[0], that.size[1] + 300])
|
||||
app.canvas.draw(true, true)
|
||||
}
|
||||
|
||||
// 以文本形式读取文件
|
||||
reader.readAsDataURL(file)
|
||||
const modelViewerVariants = preview.querySelector('model-viewer')
|
||||
const select = preview.querySelector('.variant')
|
||||
const selectMaterial = preview.querySelector('.material')
|
||||
const material_img = preview.querySelector('.material_img')
|
||||
const bg = preview.querySelector('.bg')
|
||||
|
||||
const exportGLB = preview.querySelector('.export')
|
||||
|
||||
const ddcap_step = preview.querySelector('.ddcap_step')
|
||||
const total_images = preview.querySelector('.total_images')
|
||||
const ddcap_range = preview.querySelector('.ddcap_range')
|
||||
const ddcap_range_top = preview.querySelector('.ddcap_range_top')
|
||||
const ddCap = preview.querySelector('.ddcap')
|
||||
const sleep = (t = 1000) => {
|
||||
return new Promise((res, rej) => {
|
||||
return setTimeout(() => {
|
||||
res(t)
|
||||
}, t)
|
||||
})
|
||||
}
|
||||
|
||||
async function captureImage (isUrl = true) {
|
||||
let base64Data = modelViewerVariants.toDataURL()
|
||||
|
||||
const contentType = getContentTypeFromBase64(base64Data)
|
||||
|
||||
const blob = await base64ToBlobFromURL(base64Data, contentType)
|
||||
|
||||
if (isUrl) return await uploadImage(blob, '.png')
|
||||
return await uploadImage_(blob, '.png')
|
||||
}
|
||||
|
||||
async function captureImages (angleIncrement = 1, totalImages = 12) {
|
||||
// 记录初始旋转角度
|
||||
const initialCameraOrbit =
|
||||
modelViewerVariants.cameraOrbit.split(' ')
|
||||
console.log(
|
||||
'#captureImages',
|
||||
initialCameraOrbit,
|
||||
angleIncrement * totalImages
|
||||
)
|
||||
// const totalImages = 12
|
||||
// const angleIncrement = totalRotation / totalImages // Each increment in degrees
|
||||
let currentAngle =
|
||||
Number(initialCameraOrbit[0].replace('deg', '')) -
|
||||
(angleIncrement * totalImages) / 2 // Start from the leftmost angle
|
||||
let frames = []
|
||||
|
||||
modelViewerVariants.removeAttribute('camera-controls')
|
||||
|
||||
for (let i = 0; i < totalImages; i++) {
|
||||
modelViewerVariants.cameraOrbit = `${currentAngle}deg ${initialCameraOrbit[1]} ${initialCameraOrbit[2]}`
|
||||
await sleep(1000)
|
||||
console.log(`Capturing image at angle: ${currentAngle}deg`)
|
||||
let file = await captureImage(false)
|
||||
frames.push(file)
|
||||
currentAngle += angleIncrement
|
||||
}
|
||||
await sleep(1000)
|
||||
// 恢复到初始旋转角度
|
||||
modelViewerVariants.cameraOrbit = initialCameraOrbit.join(' ')
|
||||
modelViewerVariants.setAttribute('camera-controls', '')
|
||||
return frames
|
||||
}
|
||||
ddCap.addEventListener('click', async e => {
|
||||
const angleIncrement = Number(ddcap_step.value),
|
||||
totalImages = Number(total_images.value)
|
||||
|
||||
let images = await captureImages(angleIncrement, totalImages)
|
||||
// console.log(images)
|
||||
let dd = getLocalData(key)
|
||||
dd[that.id].images = images
|
||||
setLocalDataOfWin(key, dd)
|
||||
})
|
||||
|
||||
ddcap_range.addEventListener('input', async e => {
|
||||
// console.log(ddcap_range.value)
|
||||
const initialCameraOrbit =
|
||||
modelViewerVariants.cameraOrbit.split(' ')
|
||||
modelViewerVariants.cameraOrbit = `${ddcap_range.value}deg ${initialCameraOrbit[1]} ${initialCameraOrbit[2]}`
|
||||
modelViewerVariants.setAttribute('camera-controls', '')
|
||||
})
|
||||
|
||||
ddcap_range_top.addEventListener('input', async e => {
|
||||
// console.log(ddcap_range.value)
|
||||
const initialCameraOrbit =
|
||||
modelViewerVariants.cameraOrbit.split(' ')
|
||||
modelViewerVariants.cameraOrbit = `${initialCameraOrbit[0]} ${ddcap_range_top.value}deg ${initialCameraOrbit[2]}`
|
||||
modelViewerVariants.setAttribute('camera-controls', '')
|
||||
})
|
||||
|
||||
if (modelViewerVariants) {
|
||||
modelViewerVariants.style.width = `${that.size[0] - 48}px`
|
||||
modelViewerVariants.style.height = `${that.size[1] - 48}px`
|
||||
}
|
||||
|
||||
modelViewerVariants.addEventListener('load', async () => {
|
||||
const names = modelViewerVariants.availableVariants
|
||||
|
||||
// 变量
|
||||
for (const name of names) {
|
||||
const option = document.createElement('option')
|
||||
option.value = name
|
||||
option.textContent = name
|
||||
select.appendChild(option)
|
||||
}
|
||||
// Adds a default option.
|
||||
if (names.length === 0) {
|
||||
const option = document.createElement('option')
|
||||
option.value = 'default'
|
||||
option.textContent = 'Default'
|
||||
select.appendChild(option)
|
||||
}
|
||||
|
||||
// 材质
|
||||
extractMaterial(modelViewerVariants, selectMaterial, material_img)
|
||||
})
|
||||
|
||||
let timer = null
|
||||
const delay = 500 // 延迟时间,单位为毫秒
|
||||
|
||||
async function checkCameraChange () {
|
||||
let dd = getLocalData(key)
|
||||
|
||||
// const fileBlob = new Blob([e.target.result], { type: file.type });
|
||||
let url = await captureImage()
|
||||
|
||||
let bg_blob = await base64ToBlobFromURL(
|
||||
'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mN88uXrPQAFwwK/6xJ6CQAAAABJRU5ErkJggg=='
|
||||
)
|
||||
let url_bg = await uploadImage(bg_blob, '.png')
|
||||
// console.log('url_bg',url_bg)
|
||||
|
||||
if (!dd[that.id]) {
|
||||
dd[that.id] = { url, bg: url_bg }
|
||||
} else {
|
||||
dd[that.id] = { ...dd[that.id], url }
|
||||
}
|
||||
|
||||
// 材质贴图
|
||||
let thumbUrl = material_img.getAttribute('src')
|
||||
if (thumbUrl) {
|
||||
let tb = await base64ToBlobFromURL(thumbUrl)
|
||||
let tUrl = await uploadImage(tb, '.png')
|
||||
// console.log('材质贴图', tUrl, thumbUrl)
|
||||
dd[that.id].material = tUrl
|
||||
}
|
||||
|
||||
setLocalDataOfWin(key, dd)
|
||||
}
|
||||
|
||||
function startTimer () {
|
||||
if (timer) clearTimeout(timer)
|
||||
timer = setTimeout(checkCameraChange, delay)
|
||||
}
|
||||
|
||||
modelViewerVariants.addEventListener('camera-change', startTimer)
|
||||
|
||||
select.addEventListener('input', async event => {
|
||||
modelViewerVariants.variantName =
|
||||
event.target.value === 'default' ? null : event.target.value
|
||||
// 材质
|
||||
await extractMaterial(
|
||||
modelViewerVariants,
|
||||
selectMaterial,
|
||||
material_img
|
||||
)
|
||||
checkCameraChange()
|
||||
})
|
||||
|
||||
selectMaterial.addEventListener('input', event => {
|
||||
// console.log(selectMaterial.value)
|
||||
material_img.setAttribute('src', selectMaterial.value)
|
||||
|
||||
if (selectMaterial.getAttribute('data-new-material')) {
|
||||
let index =
|
||||
~~selectMaterial.selectedOptions[0].getAttribute('data-index')
|
||||
changeMaterial(
|
||||
modelViewerVariants,
|
||||
modelViewerVariants.model.materials[index],
|
||||
selectMaterial.getAttribute('data-new-material')
|
||||
)
|
||||
}
|
||||
|
||||
checkCameraChange()
|
||||
})
|
||||
|
||||
//更新bg
|
||||
const updateBgData = (id, key, url, w, h) => {
|
||||
let dd = getLocalData(key)
|
||||
// console.log(dd[that.id],url)
|
||||
if (!dd[id]) dd[id] = { url: '', bg: url }
|
||||
dd[id] = {
|
||||
...dd[id],
|
||||
bg: url,
|
||||
bg_w: w,
|
||||
bg_h: h
|
||||
}
|
||||
setLocalDataOfWin(key, dd)
|
||||
}
|
||||
|
||||
bg.addEventListener('click', async () => {
|
||||
//更新bg
|
||||
updateBgData(that.id, key, '', 0, 0)
|
||||
preview.style.backgroundImage = 'none'
|
||||
|
||||
let base64 = await inputFileClick(false, false)
|
||||
// 将读取的文件内容设置为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)
|
||||
|
||||
//更新bg
|
||||
updateBgData(
|
||||
that.id,
|
||||
key,
|
||||
bg_url,
|
||||
bg_img.naturalWidth,
|
||||
bg_img.naturalHeight
|
||||
)
|
||||
|
||||
// 更新尺寸
|
||||
let w = that.size[0] - 48,
|
||||
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`
|
||||
})
|
||||
|
||||
exportGLB.addEventListener('click', async () => {
|
||||
const glTF = await modelViewerVariants.exportScene()
|
||||
const file = new File([glTF], 'export.glb')
|
||||
const link = document.createElement('a')
|
||||
link.download = file.name
|
||||
link.href = URL.createObjectURL(file)
|
||||
link.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] - 48,
|
||||
h = (w * bg_h) / bg_w
|
||||
|
||||
if (modelViewerVariants) {
|
||||
modelViewerVariants.style.width = `${w}px`
|
||||
modelViewerVariants.style.height = `${h}px`
|
||||
}
|
||||
preview.style.width = `${w}px`
|
||||
}
|
||||
}
|
||||
})
|
||||
return div
|
||||
}
|
||||
|
||||
let preview = document.createElement('div')
|
||||
preview.className = 'preview'
|
||||
preview.style = `margin-top: 12px;display: flex;
|
||||
preview.style = `margin-top: 12px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;background-repeat: no-repeat;background-size: contain;`
|
||||
align-items: center;background-repeat: no-repeat;
|
||||
background-size: contain;`
|
||||
|
||||
let upload = inputDiv('_mixlab_3d_image', '3D Model', preview)
|
||||
|
||||
@@ -527,7 +635,7 @@ app.registerExtension({
|
||||
if (dd[that.id]) {
|
||||
const { bg_w, bg_h } = dd[that.id]
|
||||
if (bg_h && bg_w) {
|
||||
let w = that.size[0] - 24,
|
||||
let w = that.size[0] - 48,
|
||||
h = (w * bg_h) / bg_w
|
||||
|
||||
if (modelViewerVariants) {
|
||||
@@ -561,7 +669,7 @@ app.registerExtension({
|
||||
const r = onExecuted?.apply?.(this, arguments)
|
||||
|
||||
let div = this.widgets.filter(d => d.div)[0]?.div
|
||||
console.log('Test', this.widgets)
|
||||
// console.log('Test', this.widgets)
|
||||
|
||||
let material = message.material[0]
|
||||
if (material) {
|
||||
|
||||
@@ -3,7 +3,7 @@ import { app } from '../../../scripts/app.js'
|
||||
const repoOwner = 'shadowcz007' // 替换为仓库的所有者
|
||||
const repoName = 'comfyui-mixlab-nodes' // 替换为仓库的名称
|
||||
|
||||
const version = 'v0.35.1'
|
||||
const version = 'v0.37.0'
|
||||
|
||||
fetch(`https://api.github.com/repos/${repoOwner}/${repoName}/releases/latest`)
|
||||
.then(response => response.json())
|
||||
|
||||
@@ -142,7 +142,14 @@ async function getPromptResult (category) {
|
||||
}
|
||||
|
||||
// 新的运行工作流的接口
|
||||
function queuePromptNew (filename, category, seed, input, client_id,apps=null) {
|
||||
function queuePromptNew (
|
||||
filename,
|
||||
category,
|
||||
seed,
|
||||
input,
|
||||
client_id,
|
||||
apps = null
|
||||
) {
|
||||
let url = get_url()
|
||||
// var filename = "Text-to-Image_1.json", category = "";
|
||||
|
||||
@@ -325,8 +332,9 @@ function convertImageToBlackBasedOnAlpha (image) {
|
||||
const alpha = pixels[i + 3]
|
||||
if (alpha !== 0) {
|
||||
// Set non-transparent pixels to black
|
||||
// 蒙版是黑色?
|
||||
pixels[i] = 0 // Red
|
||||
pixels[i + 1] = 0 // Green
|
||||
pixels[i + 1] = 255 // Green
|
||||
pixels[i + 2] = 0 // Blue
|
||||
}
|
||||
}
|
||||
|
||||
@@ -681,6 +681,7 @@ const addBase64ToWidgetForLoadImagesToBatch = (
|
||||
imagesWidget,
|
||||
imagesDiv
|
||||
) => {
|
||||
if(!imagesWidget.value.base64) imagesWidget.value.base64=[]
|
||||
imagesWidget.value.base64.push(base64)
|
||||
let im = createInputImageForBatch(base64, imagesWidget)
|
||||
imagesDiv.appendChild(im)
|
||||
|
||||
+10
-5
@@ -94,7 +94,7 @@ const p5InputNode = {
|
||||
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
if (nodeType.comfyClass == 'P5Input') {
|
||||
console.log('P5Input')
|
||||
// console.log('P5Input')
|
||||
const orig_nodeCreated = nodeType.prototype.onNodeCreated
|
||||
|
||||
nodeType.prototype.onNodeCreated = function () {
|
||||
@@ -171,6 +171,7 @@ const p5InputNode = {
|
||||
let widget = node.widgets?.filter(w => w.name == 'image_base64')[0]
|
||||
let framesWidget = node.widgets?.filter(w => w.name == 'frames')[0]
|
||||
if (node.type === 'P5Input' && widget) {
|
||||
console.log('#nodeCreated P5Input')
|
||||
if (framesWidget && !framesWidget.value)
|
||||
framesWidget.value = { images: [] }
|
||||
|
||||
@@ -185,15 +186,17 @@ const p5InputNode = {
|
||||
// 监听来自iframe的消息
|
||||
const ms = async event => {
|
||||
const data = event.data
|
||||
console.log('#P5 Input #', data)
|
||||
if (
|
||||
data.from === 'p5.widget' &&
|
||||
data.status === 'save' &&
|
||||
data.frames &&
|
||||
data.frames.length > 0 &&
|
||||
data.nodeId == nodeId
|
||||
data.frames.length >= 0 &&
|
||||
data.nodeId == nodeId &&
|
||||
data.id != framesWidget.value.id
|
||||
) {
|
||||
const frames = data.frames
|
||||
console.log(frames.length, nodeId)
|
||||
|
||||
//workflow会存储到local,会卡死
|
||||
framesWidget.value.images = []
|
||||
for (const f of frames) {
|
||||
@@ -201,10 +204,12 @@ const p5InputNode = {
|
||||
framesWidget.value.images.push(file)
|
||||
}
|
||||
// framesWidget.value.base64 = frames
|
||||
framesWidget.value._seed = Math.random()
|
||||
// framesWidget.value._seed = Math.random()
|
||||
node.title = 'P5 Input #' + frames.length
|
||||
framesWidget.value.id = data.id
|
||||
}
|
||||
}
|
||||
|
||||
window.addEventListener('message', ms)
|
||||
}
|
||||
}, 1000)
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
import { app } from '../../../scripts/app.js'
|
||||
import { api } from '../../../scripts/api.js'
|
||||
|
||||
function base64ToBlobFromURL (base64URL, contentType) {
|
||||
return fetch(base64URL).then(response => response.blob())
|
||||
}
|
||||
|
||||
async function uploadImage (blob, fileType = '.svg', filename) {
|
||||
// const blob = await (await fetch(src)).blob();
|
||||
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
|
||||
})
|
||||
|
||||
// console.log(resp)
|
||||
let data = await resp.json()
|
||||
|
||||
return data
|
||||
}
|
||||
|
||||
// 上传得到url
|
||||
async function uploadBase64ToFile (base64) {
|
||||
let bg_blob = await base64ToBlobFromURL(base64)
|
||||
let url = await uploadImage(bg_blob, '.png')
|
||||
return url
|
||||
}
|
||||
|
||||
class Visualizer {
|
||||
constructor (node, container, visualSrc) {
|
||||
this.node = node
|
||||
|
||||
this.iframe = document.createElement('iframe')
|
||||
Object.assign(this.iframe, {
|
||||
scrolling: 'no',
|
||||
overflow: 'hidden'
|
||||
})
|
||||
this.iframe.src = '/extensions/comfyui-mixlab-nodes/' + visualSrc + '.html'
|
||||
console.log('#Visualizer', container, this.iframe)
|
||||
container.appendChild(this.iframe)
|
||||
}
|
||||
|
||||
updateVisual (params) {
|
||||
console.log('#updateVisual', params, this.iframe)
|
||||
// const iframeDocument = this.iframe.contentWindow.document
|
||||
// const previewScript = iframeDocument.getElementById('visualizer')
|
||||
// previewScript.setAttribute(
|
||||
// 'reference_image',
|
||||
// JSON.stringify(params.reference_image)
|
||||
// )
|
||||
// previewScript.setAttribute('depth_map', JSON.stringify(params.depth_map))
|
||||
// Update the reference image and depth map
|
||||
this.iframe.contentWindow.postMessage(params, '*')
|
||||
}
|
||||
|
||||
remove () {
|
||||
this.container.remove()
|
||||
}
|
||||
}
|
||||
|
||||
function createVisualizer (node, inputName, typeName, inputData, app) {
|
||||
node.name = inputName
|
||||
|
||||
const widget = {
|
||||
type: typeName,
|
||||
name: 'preview3d',
|
||||
callback: () => {},
|
||||
draw: function (ctx, node, widgetWidth, widgetY, widgetHeight) {
|
||||
const margin = 10
|
||||
const top_offset = 5
|
||||
const visible = app.canvas.ds.scale > 0.5 && this.type === typeName
|
||||
const w = widgetWidth - margin * 4
|
||||
const clientRectBound = ctx.canvas.getBoundingClientRect()
|
||||
const transform = new DOMMatrix()
|
||||
.scaleSelf(
|
||||
clientRectBound.width / ctx.canvas.width,
|
||||
clientRectBound.height / ctx.canvas.height
|
||||
)
|
||||
.multiplySelf(ctx.getTransform())
|
||||
.translateSelf(margin, margin + widgetY)
|
||||
|
||||
Object.assign(this.visualizer.style, {
|
||||
left: `${transform.a * margin + transform.e}px`,
|
||||
top: `${transform.d + transform.f + top_offset}px`,
|
||||
width: `${w * transform.a}px`,
|
||||
height: `${
|
||||
w * transform.d - widgetHeight - margin * 15 * transform.d
|
||||
}px`,
|
||||
position: 'absolute',
|
||||
overflow: 'hidden',
|
||||
zIndex: app.graph._nodes.indexOf(node)
|
||||
})
|
||||
|
||||
Object.assign(this.visualizer.children[0].style, {
|
||||
transformOrigin: '50% 50%',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
border: '0 none'
|
||||
})
|
||||
|
||||
this.visualizer.hidden = !visible
|
||||
}
|
||||
}
|
||||
|
||||
const container = document.createElement('div')
|
||||
container.id = `Comfy3D_${inputName}`
|
||||
|
||||
node.visualizer = new Visualizer(node, container, typeName)
|
||||
widget.visualizer = container
|
||||
widget.parent = node
|
||||
|
||||
document.body.appendChild(widget.visualizer)
|
||||
|
||||
node.addCustomWidget(widget)
|
||||
|
||||
node.updateParameters = params => {
|
||||
// console.log('#updateParameters', params)
|
||||
params.id = node.id
|
||||
// node.visualizer = new Visualizer(node, container, typeName)
|
||||
node.visualizer.updateVisual(params)
|
||||
}
|
||||
|
||||
// Events for drawing backgound
|
||||
node.onDrawBackground = function (ctx) {
|
||||
if (!this.flags.collapsed) {
|
||||
node.visualizer.iframe.hidden = false
|
||||
} else {
|
||||
node.visualizer.iframe.hidden = true
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure visualization iframe is always inside the node when resize the node
|
||||
node.onResize = function () {
|
||||
let [w, h] = this.size
|
||||
if (w <= 600) w = 600
|
||||
if (h <= 500) h = 500
|
||||
|
||||
if (w > 600) {
|
||||
h = w - 100
|
||||
}
|
||||
|
||||
this.size = [w, h]
|
||||
}
|
||||
|
||||
// Events for remove nodes
|
||||
node.onRemoved = () => {
|
||||
for (let w in node.widgets) {
|
||||
if (node.widgets[w].visualizer) {
|
||||
node.widgets[w].visualizer.remove()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
widget: widget
|
||||
}
|
||||
}
|
||||
|
||||
function registerVisualizer (nodeType, nodeData, nodeClassName, typeName) {
|
||||
if (nodeData.name == nodeClassName) {
|
||||
const onNodeCreated = nodeType.prototype.onNodeCreated
|
||||
|
||||
nodeType.prototype.onNodeCreated = async function () {
|
||||
const r = onNodeCreated ? onNodeCreated.apply(this, arguments) : undefined
|
||||
|
||||
let Preview3DNode = app.graph._nodes.filter(
|
||||
wi => wi.type == nodeClassName
|
||||
)
|
||||
let nodeName = `Preview3DNode_${Preview3DNode.length}`
|
||||
|
||||
const result = await createVisualizer.apply(this, [
|
||||
this,
|
||||
nodeName,
|
||||
typeName,
|
||||
{},
|
||||
app
|
||||
])
|
||||
|
||||
this.setSize([600, 500])
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
nodeType.prototype.onExecuted = async function (message) {
|
||||
// Check if reference image and depth map are available
|
||||
if (message.reference_image && message.depth_map) {
|
||||
const params = {}
|
||||
params.reference_image = message.reference_image[0]
|
||||
params.depth_map = message.depth_map[0]
|
||||
this.updateParameters(params)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
app.registerExtension({
|
||||
name: 'Mixlab.nodes.depthviewer',
|
||||
|
||||
async beforeRegisterNodeDef (nodeType, nodeData, app) {
|
||||
registerVisualizer(nodeType, nodeData, 'DepthViewer', 'threeVisualizer')
|
||||
},
|
||||
nodeCreated (node, app) {
|
||||
//数据延迟??
|
||||
setTimeout(() => {
|
||||
let widget = node.widgets?.filter(w => w.name == 'preview3d')[0]
|
||||
let framesWidget = node.widgets?.filter(w => w.name == 'frames')[0]
|
||||
|
||||
if (node.type === 'DepthViewer' && widget) {
|
||||
let nodeId = node.id
|
||||
//延迟才能获得this.id
|
||||
widget.visualizer.querySelector('iframe').src += '?id=' + nodeId
|
||||
// console.log('DepthViewer',widget)
|
||||
window.addEventListener('message', async event => {
|
||||
// 检查消息的来源,确保消息来自可信的源
|
||||
console.log(event)
|
||||
const { id, imgs } = event.data
|
||||
if (id == nodeId) {
|
||||
framesWidget.value = { images: [] }
|
||||
|
||||
for (const f of imgs) {
|
||||
let file = await uploadBase64ToFile(f)
|
||||
framesWidget.value.images.push(file)
|
||||
}
|
||||
// framesWidget.value.base64 = frames
|
||||
framesWidget.value._seed = Math.random()
|
||||
node.title = 'Input #' + imgs.length
|
||||
}
|
||||
})
|
||||
}
|
||||
}, 1000)
|
||||
}
|
||||
})
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,144 @@
|
||||
/**
|
||||
* https://github.com/google/model-viewer/blob/master/packages/model-viewer/src/three-components/EnvironmentScene.ts
|
||||
*/
|
||||
|
||||
import {
|
||||
BackSide,
|
||||
BoxGeometry,
|
||||
Mesh,
|
||||
MeshBasicMaterial,
|
||||
MeshStandardMaterial,
|
||||
PointLight,
|
||||
Scene,
|
||||
} from './three.module.js';
|
||||
|
||||
class RoomEnvironment extends Scene {
|
||||
|
||||
constructor( renderer = null ) {
|
||||
|
||||
super();
|
||||
|
||||
const geometry = new BoxGeometry();
|
||||
geometry.deleteAttribute( 'uv' );
|
||||
|
||||
const roomMaterial = new MeshStandardMaterial( { side: BackSide } );
|
||||
const boxMaterial = new MeshStandardMaterial();
|
||||
|
||||
const mainLight = new PointLight( 0xffffff, 900, 28, 2 );
|
||||
mainLight.position.set( 0.418, 16.199, 0.300 );
|
||||
this.add( mainLight );
|
||||
|
||||
const room = new Mesh( geometry, roomMaterial );
|
||||
room.position.set( - 0.757, 13.219, 0.717 );
|
||||
room.scale.set( 31.713, 28.305, 28.591 );
|
||||
this.add( room );
|
||||
|
||||
const box1 = new Mesh( geometry, boxMaterial );
|
||||
box1.position.set( - 10.906, 2.009, 1.846 );
|
||||
box1.rotation.set( 0, - 0.195, 0 );
|
||||
box1.scale.set( 2.328, 7.905, 4.651 );
|
||||
this.add( box1 );
|
||||
|
||||
const box2 = new Mesh( geometry, boxMaterial );
|
||||
box2.position.set( - 5.607, - 0.754, - 0.758 );
|
||||
box2.rotation.set( 0, 0.994, 0 );
|
||||
box2.scale.set( 1.970, 1.534, 3.955 );
|
||||
this.add( box2 );
|
||||
|
||||
const box3 = new Mesh( geometry, boxMaterial );
|
||||
box3.position.set( 6.167, 0.857, 7.803 );
|
||||
box3.rotation.set( 0, 0.561, 0 );
|
||||
box3.scale.set( 3.927, 6.285, 3.687 );
|
||||
this.add( box3 );
|
||||
|
||||
const box4 = new Mesh( geometry, boxMaterial );
|
||||
box4.position.set( - 2.017, 0.018, 6.124 );
|
||||
box4.rotation.set( 0, 0.333, 0 );
|
||||
box4.scale.set( 2.002, 4.566, 2.064 );
|
||||
this.add( box4 );
|
||||
|
||||
const box5 = new Mesh( geometry, boxMaterial );
|
||||
box5.position.set( 2.291, - 0.756, - 2.621 );
|
||||
box5.rotation.set( 0, - 0.286, 0 );
|
||||
box5.scale.set( 1.546, 1.552, 1.496 );
|
||||
this.add( box5 );
|
||||
|
||||
const box6 = new Mesh( geometry, boxMaterial );
|
||||
box6.position.set( - 2.193, - 0.369, - 5.547 );
|
||||
box6.rotation.set( 0, 0.516, 0 );
|
||||
box6.scale.set( 3.875, 3.487, 2.986 );
|
||||
this.add( box6 );
|
||||
|
||||
|
||||
// -x right
|
||||
const light1 = new Mesh( geometry, createAreaLightMaterial( 50 ) );
|
||||
light1.position.set( - 16.116, 14.37, 8.208 );
|
||||
light1.scale.set( 0.1, 2.428, 2.739 );
|
||||
this.add( light1 );
|
||||
|
||||
// -x left
|
||||
const light2 = new Mesh( geometry, createAreaLightMaterial( 50 ) );
|
||||
light2.position.set( - 16.109, 18.021, - 8.207 );
|
||||
light2.scale.set( 0.1, 2.425, 2.751 );
|
||||
this.add( light2 );
|
||||
|
||||
// +x
|
||||
const light3 = new Mesh( geometry, createAreaLightMaterial( 17 ) );
|
||||
light3.position.set( 14.904, 12.198, - 1.832 );
|
||||
light3.scale.set( 0.15, 4.265, 6.331 );
|
||||
this.add( light3 );
|
||||
|
||||
// +z
|
||||
const light4 = new Mesh( geometry, createAreaLightMaterial( 43 ) );
|
||||
light4.position.set( - 0.462, 8.89, 14.520 );
|
||||
light4.scale.set( 4.38, 5.441, 0.088 );
|
||||
this.add( light4 );
|
||||
|
||||
// -z
|
||||
const light5 = new Mesh( geometry, createAreaLightMaterial( 20 ) );
|
||||
light5.position.set( 3.235, 11.486, - 12.541 );
|
||||
light5.scale.set( 2.5, 2.0, 0.1 );
|
||||
this.add( light5 );
|
||||
|
||||
// +y
|
||||
const light6 = new Mesh( geometry, createAreaLightMaterial( 100 ) );
|
||||
light6.position.set( 0.0, 20.0, 0.0 );
|
||||
light6.scale.set( 1.0, 0.1, 1.0 );
|
||||
this.add( light6 );
|
||||
|
||||
}
|
||||
|
||||
dispose() {
|
||||
|
||||
const resources = new Set();
|
||||
|
||||
this.traverse( ( object ) => {
|
||||
|
||||
if ( object.isMesh ) {
|
||||
|
||||
resources.add( object.geometry );
|
||||
resources.add( object.material );
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
for ( const resource of resources ) {
|
||||
|
||||
resource.dispose();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function createAreaLightMaterial( intensity ) {
|
||||
|
||||
const material = new MeshBasicMaterial();
|
||||
material.color.setScalar( intensity );
|
||||
return material;
|
||||
|
||||
}
|
||||
|
||||
export { RoomEnvironment };
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,316 @@
|
||||
import * as THREE from './three/three.module.js'
|
||||
import { api } from '../../../scripts/api.js'
|
||||
import { OrbitControls } from './three/OrbitControls.js'
|
||||
import { RoomEnvironment } from './three/RoomEnvironment.js'
|
||||
|
||||
const visualizer = document.getElementById('visualizer')
|
||||
const container = document.getElementById('container')
|
||||
const progressDialog = document.getElementById('progress-dialog')
|
||||
const progressIndicator = document.getElementById('progress-indicator')
|
||||
|
||||
const renderer = new THREE.WebGLRenderer({
|
||||
antialias: true,
|
||||
extensions: {
|
||||
derivatives: true
|
||||
}
|
||||
})
|
||||
renderer.setPixelRatio(window.devicePixelRatio)
|
||||
renderer.setSize(window.innerWidth, window.innerHeight)
|
||||
|
||||
if (container) container.appendChild(renderer.domElement)
|
||||
|
||||
const pmremGenerator = new THREE.PMREMGenerator(renderer)
|
||||
|
||||
// scene
|
||||
const scene = new THREE.Scene()
|
||||
scene.background = new THREE.Color(0x000000)
|
||||
scene.environment = pmremGenerator.fromScene(
|
||||
new RoomEnvironment(renderer),
|
||||
0.04
|
||||
).texture
|
||||
|
||||
const ambientLight = new THREE.AmbientLight(0xffffff)
|
||||
|
||||
const camera = new THREE.PerspectiveCamera(
|
||||
40,
|
||||
window.innerWidth / window.innerHeight,
|
||||
0.1,
|
||||
1000
|
||||
)
|
||||
camera.position.set(0, 0, 10)
|
||||
const pointLight = new THREE.PointLight(0xffffff, 15)
|
||||
camera.add(pointLight)
|
||||
|
||||
const controls = new OrbitControls(camera, renderer.domElement)
|
||||
controls.target.set(0, 0, 0)
|
||||
controls.update()
|
||||
controls.enablePan = true
|
||||
controls.enableDamping = true
|
||||
|
||||
// Handle window resize event
|
||||
window.onresize = function () {
|
||||
camera.aspect = window.innerWidth / window.innerHeight
|
||||
camera.updateProjectionMatrix()
|
||||
|
||||
renderer.setSize(window.innerWidth, window.innerHeight)
|
||||
}
|
||||
|
||||
var lastReferenceImage = ''
|
||||
var lastDepthMap = ''
|
||||
var needUpdate = false
|
||||
|
||||
function frameUpdate () {
|
||||
var referenceImage = visualizer?.getAttribute('reference_image')
|
||||
var depthMap = visualizer?.getAttribute('depth_map')
|
||||
if (referenceImage == lastReferenceImage && depthMap == lastDepthMap) {
|
||||
if (needUpdate) {
|
||||
controls.update()
|
||||
renderer.render(scene, camera)
|
||||
}
|
||||
requestAnimationFrame(frameUpdate)
|
||||
} else {
|
||||
needUpdate = false
|
||||
scene.clear()
|
||||
if (progressDialog) progressDialog.open = true
|
||||
lastReferenceImage = referenceImage
|
||||
lastDepthMap = depthMap
|
||||
if (lastReferenceImage && lastReferenceImage != 'undefined') {
|
||||
// console.log('lastReferenceImage',typeof(lastReferenceImage),lastDepthMap)
|
||||
main(JSON.parse(lastReferenceImage), JSON.parse(lastDepthMap))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const onProgress = function (xhr) {
|
||||
if (xhr.lengthComputable) {
|
||||
progressIndicator.value = (xhr.loaded / xhr.total) * 100
|
||||
}
|
||||
}
|
||||
|
||||
const onError = function (e) {
|
||||
console.error(e)
|
||||
}
|
||||
|
||||
async function main (referenceImageParams, depthMapParams) {
|
||||
let referenceTexture, depthTexture
|
||||
let imageWidth = 10 // Default width
|
||||
let imageHeight = 10 // Default height, will be updated based on the image's aspect ratio
|
||||
// console.log('#referenceImageParams', referenceImageParams)
|
||||
if (referenceImageParams?.filename) {
|
||||
const referenceImageUrl = api
|
||||
.apiURL('/view?' + new URLSearchParams(referenceImageParams))
|
||||
.replace(/extensions.*\//, '')
|
||||
const referenceImageExt = referenceImageParams.filename.slice(
|
||||
referenceImageParams.filename.lastIndexOf('.') + 1
|
||||
)
|
||||
|
||||
if (
|
||||
referenceImageExt === 'png' ||
|
||||
referenceImageExt === 'jpg' ||
|
||||
referenceImageExt === 'jpeg'
|
||||
) {
|
||||
const referenceImageLoader = new THREE.TextureLoader()
|
||||
referenceTexture = await new Promise((resolve, reject) => {
|
||||
referenceImageLoader.load(
|
||||
referenceImageUrl,
|
||||
texture => {
|
||||
// Once the image is loaded, update the width and height based on the image's aspect ratio
|
||||
imageWidth = 10 // Keep the width as 10
|
||||
imageHeight = texture.image.height / (texture.image.width / 10)
|
||||
resolve(texture)
|
||||
},
|
||||
undefined,
|
||||
reject
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (depthMapParams?.filename) {
|
||||
const depthMapUrl = api
|
||||
.apiURL('/view?' + new URLSearchParams(depthMapParams))
|
||||
.replace(/extensions.*\//, '')
|
||||
const depthMapExt = depthMapParams.filename.slice(
|
||||
depthMapParams.filename.lastIndexOf('.') + 1
|
||||
)
|
||||
|
||||
if (
|
||||
depthMapExt === 'png' ||
|
||||
depthMapExt === 'jpg' ||
|
||||
depthMapExt === 'jpeg'
|
||||
) {
|
||||
const depthMapLoader = new THREE.TextureLoader()
|
||||
depthTexture = await depthMapLoader.loadAsync(depthMapUrl)
|
||||
}
|
||||
}
|
||||
|
||||
if (referenceTexture && depthTexture) {
|
||||
const depthMaterial = new THREE.ShaderMaterial({
|
||||
uniforms: {
|
||||
referenceTexture: { value: referenceTexture },
|
||||
depthTexture: { value: depthTexture },
|
||||
depthScale: { value: 5.0 },
|
||||
ambientLightColor: { value: new THREE.Color(0.2, 0.2, 0.2) },
|
||||
lightPosition: { value: new THREE.Vector3(2, 2, 2) },
|
||||
lightColor: { value: new THREE.Color(1, 1, 1) },
|
||||
lightIntensity: { value: 1.0 },
|
||||
shininess: { value: 30 }
|
||||
},
|
||||
vertexShader: `
|
||||
uniform sampler2D depthTexture;
|
||||
uniform float depthScale;
|
||||
|
||||
varying vec2 vUv;
|
||||
varying float vDepth;
|
||||
varying vec3 vNormal;
|
||||
varying vec3 vViewPosition;
|
||||
|
||||
void main() {
|
||||
vUv = uv;
|
||||
|
||||
float depth = texture2D(depthTexture, uv).r;
|
||||
vec3 displacement = normal * depth * depthScale;
|
||||
vec3 displacedPosition = position + displacement;
|
||||
|
||||
vec4 worldPosition = modelMatrix * vec4(displacedPosition, 1.0);
|
||||
vNormal = normalize(normalMatrix * normal);
|
||||
vViewPosition = (viewMatrix * worldPosition).xyz;
|
||||
|
||||
gl_Position = projectionMatrix * viewMatrix * worldPosition;
|
||||
|
||||
vDepth = depth;
|
||||
}
|
||||
`,
|
||||
fragmentShader: `
|
||||
uniform sampler2D referenceTexture;
|
||||
|
||||
varying vec2 vUv;
|
||||
varying float vDepth;
|
||||
|
||||
void main() {
|
||||
vec4 referenceColor = texture2D(referenceTexture, vUv);
|
||||
|
||||
// Directly use reference color without fog
|
||||
gl_FragColor = referenceColor;
|
||||
}
|
||||
`
|
||||
})
|
||||
|
||||
const planeGeometry = new THREE.PlaneGeometry(
|
||||
imageWidth,
|
||||
imageHeight,
|
||||
200,
|
||||
200
|
||||
)
|
||||
const depthMesh = new THREE.Mesh(planeGeometry, depthMaterial)
|
||||
scene.add(depthMesh)
|
||||
}
|
||||
|
||||
needUpdate = true
|
||||
|
||||
scene.add(ambientLight)
|
||||
scene.add(camera)
|
||||
|
||||
progressDialog?.close()
|
||||
|
||||
frameUpdate()
|
||||
}
|
||||
|
||||
document
|
||||
.getElementById('screenshotButton')
|
||||
?.addEventListener('click', takeScreenshot)
|
||||
|
||||
const sleep = (t = 1000) => {
|
||||
return new Promise((res, rej) => {
|
||||
setTimeout(() => res(1), t)
|
||||
})
|
||||
}
|
||||
|
||||
// 方法:旋转摄像机并拍摄图片 // 每次旋转的角度增量,转换为弧度
|
||||
async function captureImages (
|
||||
totalFrames = 40,
|
||||
angleIncrement = THREE.MathUtils.degToRad(0.5)
|
||||
) {
|
||||
// 计算场景中所有物体的中心点
|
||||
const box = new THREE.Box3().setFromObject(scene)
|
||||
const center = new THREE.Vector3()
|
||||
box.getCenter(center)
|
||||
|
||||
// 计算当前相机距离中心点的半径
|
||||
const radius = camera.position.distanceTo(center)
|
||||
|
||||
// 存储图片的数组
|
||||
let images = []
|
||||
|
||||
// 记录初始相机位置和朝向
|
||||
const initialPosition = camera.position.clone()
|
||||
const initialTarget = center.clone()
|
||||
|
||||
// 计算当前相机的初始角度
|
||||
const initialAngle = Math.atan2(
|
||||
camera.position.z - center.z,
|
||||
camera.position.x - center.x
|
||||
)
|
||||
|
||||
// 起始角度为从当前角度往左旋转 20 度的位置
|
||||
const startAngle = initialAngle - (angleIncrement * totalFrames) / 2
|
||||
|
||||
for (let i = 0; i < totalFrames; i++) {
|
||||
const angle = startAngle + i * angleIncrement
|
||||
|
||||
// 计算相机的位置
|
||||
camera.position.x = center.x + radius * Math.cos(angle)
|
||||
camera.position.z = center.z + radius * Math.sin(angle)
|
||||
camera.position.y = initialPosition.y // 保持相机高度不变
|
||||
camera.lookAt(center) // 相机看向中心点
|
||||
|
||||
// 渲染当前帧
|
||||
renderer.render(scene, camera)
|
||||
|
||||
// 将当前帧保存为图片
|
||||
const imgData = renderer.domElement.toDataURL('image/png')
|
||||
images.push(imgData)
|
||||
|
||||
// 等待一段时间
|
||||
await new Promise(resolve => setTimeout(resolve, 500))
|
||||
}
|
||||
|
||||
// 恢复相机到初始位置和朝向
|
||||
camera.position.copy(initialPosition)
|
||||
camera.lookAt(initialTarget)
|
||||
|
||||
return images
|
||||
}
|
||||
|
||||
async function takeScreenshot () {
|
||||
// 更新相机的矩阵,以确保其世界矩阵是最新的
|
||||
camera.updateMatrixWorld()
|
||||
const imgs = await captureImages()
|
||||
|
||||
// 获取当前网页的 URL
|
||||
const currentUrl = window.location.href
|
||||
|
||||
// 创建一个 URL 对象
|
||||
const url = new URL(currentUrl)
|
||||
|
||||
// 使用 URLSearchParams 获取参数
|
||||
const params = new URLSearchParams(url.search)
|
||||
|
||||
// 获取参数 'id' 的值
|
||||
const id = params.get('id')
|
||||
|
||||
window.parent.postMessage({ imgs, id }, '*')
|
||||
}
|
||||
|
||||
main()
|
||||
|
||||
window.addEventListener('message', event => {
|
||||
// 这里可以添加对来源的验证,以确保安全
|
||||
// console.log('Message received from parent page:', event.data)
|
||||
let { reference_image, depth_map } = event.data
|
||||
if (reference_image && depth_map) {
|
||||
visualizer?.setAttribute('reference_image', JSON.stringify(reference_image))
|
||||
visualizer?.setAttribute('depth_map', JSON.stringify(depth_map))
|
||||
frameUpdate()
|
||||
}
|
||||
})
|
||||
@@ -73,28 +73,22 @@
|
||||
}
|
||||
initialContent = initialContent.replace(/\r\n/g, '\n').trim();
|
||||
ReactDOM.render(React.createElement(app_1.default, {initialContent: initialContent, autosaver: new autosaver_1.SessionStorageAutosaver(id), baseSketchURL: baseSketchURL, p5version: p5version, previewWidth: previewWidth, maxRunTime: maxRunTime, autoplay: autoplay}), document.getElementById('app-holder'));
|
||||
// 监听来自iframe的消息
|
||||
window.addEventListener('message', function (event) {
|
||||
var data = event.data;
|
||||
// console.log("#main", data)
|
||||
if (data.from === 'p5.widget' && data.status === 'save') {
|
||||
var frames_1 = data.frames;
|
||||
window.parent.postMessage({
|
||||
frames: frames_1,
|
||||
from: 'p5.widget',
|
||||
status: 'save',
|
||||
_from: "main"
|
||||
}, '*');
|
||||
}
|
||||
});
|
||||
}
|
||||
window.addEventListener('load', start);
|
||||
// 监听来自iframe的消息
|
||||
window.addEventListener('message', function (event) {
|
||||
var data = event.data;
|
||||
if (data.from === 'p5.widget' && data.status === 'save') {
|
||||
var frames_1 = data.frames;
|
||||
window.parent.postMessage({
|
||||
frames: frames_1,
|
||||
from: 'p5.widget',
|
||||
status: 'save'
|
||||
}, '*');
|
||||
}
|
||||
// if (data.from === 'p5.widget' && data.status === 'capture') {
|
||||
// window.parent.postMessage({
|
||||
// from: 'p5.widget',
|
||||
// status: 'capture',
|
||||
// frameCount: data.frameCount,
|
||||
// maxCount: data.maxCount,
|
||||
// }, '*');
|
||||
// }
|
||||
});
|
||||
|
||||
|
||||
/***/ }),
|
||||
@@ -23670,7 +23664,7 @@
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
"use strict";
|
||||
exports.P5_VERSION = '0.4.23';
|
||||
exports.P5_VERSION = '1.10.0';
|
||||
exports.PREVIEW_WIDTH = 150;
|
||||
exports.HEIGHT = 300;
|
||||
exports.MAX_RUN_TIME = 1000;
|
||||
@@ -23766,6 +23760,10 @@
|
||||
};
|
||||
this.handleStopClick = function () {
|
||||
_this.setState({ isPlaying: false });
|
||||
window.parent.postMessage({
|
||||
from: 'p5.widget',
|
||||
status: 'stop'
|
||||
}, '*');
|
||||
};
|
||||
this.handleUndoClick = function () {
|
||||
_this.refs.editor.undo();
|
||||
@@ -23956,7 +23954,7 @@
|
||||
}
|
||||
Toolbar.prototype.render = function () {
|
||||
return (React.createElement("div", {className: "toolbar"}, React.createElement("a", {className: "p5-logo", href: "http://p5js.org/", target: "_blank"}, React.createElement("img", {src: "static/img/p5js-beta.svg", alt: "p5js.org"})), React.createElement("button", {onClick: this.props.onPlayClick}, React.createElement(OpenIconicMediaPlay, null), "Play"), this.props.onStopClick
|
||||
? React.createElement("button", {onClick: this.props.onStopClick}, React.createElement(OpenIconicMediaStop, null), "Stop ", this.props.progress)
|
||||
? React.createElement("button", {onClick: this.props.onStopClick}, React.createElement(OpenIconicMediaStop, null), "Stop")
|
||||
: null, this.props.onUndoClick
|
||||
? React.createElement("button", {onClick: this.props.onUndoClick}, React.createElement(OpenIconicActionUndo, null), "Undo")
|
||||
: null, this.props.onRedoClick
|
||||
@@ -34945,8 +34943,7 @@
|
||||
content = implicit_sketch_1.default(content);
|
||||
try {
|
||||
content = falafel_1.default(content, {}, loop_inserter_1.default(function (node) {
|
||||
return LOOP_CHECK_FUNC_NAME + "(" +
|
||||
JSON.stringify(node.range) + ");";
|
||||
return LOOP_CHECK_FUNC_NAME + "(" + JSON.stringify(node.range) + ");";
|
||||
})).toString();
|
||||
}
|
||||
catch (e) {
|
||||
@@ -34998,6 +34995,7 @@
|
||||
"use strict";
|
||||
var esprima = __webpack_require__(204);
|
||||
var parse = esprima.parse;
|
||||
var LOOP_CHECK_FUNC_NAME = '__loopCheck';
|
||||
function default_1(src, opts, fn) {
|
||||
if (typeof opts === 'function') {
|
||||
fn = opts;
|
||||
@@ -35080,7 +35078,6 @@
|
||||
console.log('#Found draw function:', node);
|
||||
var hasCapturerStart_1 = false;
|
||||
var hasCapturerEnd_1 = false;
|
||||
// Traverse the function body to check for `capturer_start()` and `capturer_end()`
|
||||
(function checkCapturerCalls(bodyNode) {
|
||||
if (bodyNode.type === 'CallExpression' &&
|
||||
bodyNode.callee &&
|
||||
@@ -40865,25 +40862,48 @@
|
||||
"use strict";
|
||||
function defaultInserter(name) {
|
||||
return function (node) {
|
||||
return name + "();";
|
||||
return name + '();';
|
||||
};
|
||||
}
|
||||
var LOOP_CHECK_FUNC_NAME = '__loopCheck';
|
||||
function LoopInserter(fn) {
|
||||
if (typeof (fn) == "string")
|
||||
fn = defaultInserter(fn);
|
||||
return function (node) {
|
||||
if (node.type == 'WhileStatement' ||
|
||||
node.type == 'ForStatement' ||
|
||||
node.type == 'DoWhileStatement') {
|
||||
node.body.update('{ ' + fn(node) + node.body.source() + ' }');
|
||||
return true;
|
||||
if (node.type === 'ForStatement' ||
|
||||
node.type === 'WhileStatement' ||
|
||||
node.type === 'DoWhileStatement') {
|
||||
var loopCheckCode = {
|
||||
type: 'ExpressionStatement',
|
||||
expression: {
|
||||
type: 'CallExpression',
|
||||
callee: {
|
||||
type: 'Identifier',
|
||||
name: LOOP_CHECK_FUNC_NAME
|
||||
},
|
||||
arguments: [
|
||||
{
|
||||
type: 'Literal',
|
||||
value: JSON.stringify(node.range)
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
if (node.body.type === 'BlockStatement') {
|
||||
// Insert the loop check at the beginning of the block
|
||||
node.body.body.unshift(loopCheckCode);
|
||||
}
|
||||
else {
|
||||
// Wrap the single statement body in a block and insert the loop check
|
||||
node.body = {
|
||||
type: 'BlockStatement',
|
||||
body: [loopCheckCode, node.body]
|
||||
};
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return '';
|
||||
};
|
||||
}
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.default = LoopInserter;
|
||||
;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
||||
<!DOCTYPE html>
|
||||
<meta charset="utf-8">
|
||||
<link href="https://fonts.googleapis.com/css?family=Montserrat" rel="stylesheet">
|
||||
<!-- <link href="https://fonts.googleapis.com/css?family=Montserrat" rel="stylesheet"> -->
|
||||
<title>p5.js-widget</title>
|
||||
<div id="app-holder"></div>
|
||||
<script src="./main.bundle.js"></script>
|
||||
|
||||
@@ -229,7 +229,7 @@
|
||||
/***/ (function(module, exports) {
|
||||
|
||||
"use strict";
|
||||
exports.P5_VERSION = '0.4.23';
|
||||
exports.P5_VERSION = '1.10.0';
|
||||
exports.PREVIEW_WIDTH = 150;
|
||||
exports.HEIGHT = 300;
|
||||
exports.MAX_RUN_TIME = 1000;
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -54,7 +54,7 @@
|
||||
cb = cb || (function () { });
|
||||
script.onload = cb;
|
||||
script.onerror = function () {
|
||||
console.log("Failed to load script: " + url);
|
||||
console.log('Failed to load script: ' + url);
|
||||
};
|
||||
script.setAttribute('src', url);
|
||||
document.body.appendChild(script);
|
||||
@@ -92,7 +92,7 @@
|
||||
if (Date.now() - startTime > maxRunTime) {
|
||||
self.wasTriggered = true;
|
||||
loopCheckFailureRange = range;
|
||||
throw new Error("Loop took over " + maxRunTime + " ms to run");
|
||||
throw new Error('Loop took over ' + maxRunTime + ' ms to run');
|
||||
}
|
||||
};
|
||||
setInterval(function () {
|
||||
@@ -115,11 +115,12 @@
|
||||
global.addEventListener('error', function (e) {
|
||||
var message = e.message;
|
||||
var line = undefined;
|
||||
// console.log(message)
|
||||
if (loopChecker.wasTriggered) {
|
||||
message = "Your loop is taking too long to run.";
|
||||
message = 'Your loop is taking too long to run.';
|
||||
line = loopChecker.getLineNumber();
|
||||
}
|
||||
else if (typeof (e.lineno) === 'number' &&
|
||||
else if (typeof e.lineno === 'number' &&
|
||||
(e.filename === '' || e.filename === window.location.href)) {
|
||||
line = e.lineno;
|
||||
}
|
||||
@@ -131,12 +132,15 @@
|
||||
catch (e) { }
|
||||
errorCb(message, line);
|
||||
});
|
||||
loadScripts([
|
||||
p5url(p5version),
|
||||
], function () {
|
||||
loadScripts([p5url(p5version)], function () {
|
||||
document.body.appendChild(sketchScript);
|
||||
if (document.readyState === 'complete') {
|
||||
new global.p5();
|
||||
try {
|
||||
new global.p5();
|
||||
}
|
||||
catch (e) {
|
||||
console.error('Failed to initialize p5:', e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -3,14 +3,8 @@
|
||||
<title>Preview</title>
|
||||
|
||||
<body>
|
||||
<script src="./p5.js"></script>
|
||||
|
||||
<!-- <script src="./p5.js"></script> -->
|
||||
<script src="./src/CCapture.js"></script>
|
||||
<!-- <script src="src/gif.js"></script> -->
|
||||
<!-- <script src="src/gif.worker.js"></script> -->
|
||||
<!-- <script src="src/download.js"></script> -->
|
||||
<!-- <script src="src/tar.js"></script> -->
|
||||
|
||||
<script>
|
||||
let capturer = new CCapture({
|
||||
format: 'png',
|
||||
@@ -19,24 +13,42 @@
|
||||
});
|
||||
|
||||
|
||||
var capturer_start = () => {
|
||||
if (frameCount === 1) {
|
||||
var capturer_start = (t = 1) => {
|
||||
if (frameCount === t && capturer) {
|
||||
capturer.start();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var capturer_end = (t = 24) => {
|
||||
|
||||
if (frameCount < t) {
|
||||
// console.log(frameCount < t, frameCount, t)
|
||||
if (frameCount < t && capturer) {
|
||||
capturer.capture(canvas, t);
|
||||
} else if (frameCount === t) {
|
||||
capturer.save(null);
|
||||
} else if (capturer) {
|
||||
capturer.save((frames) => {
|
||||
window.parent.postMessage(
|
||||
{
|
||||
frames,
|
||||
from: 'p5.widget',
|
||||
status: 'save',
|
||||
encoder: true
|
||||
},
|
||||
'*'
|
||||
)
|
||||
});
|
||||
capturer.stop();
|
||||
capturer = null;
|
||||
}
|
||||
}
|
||||
|
||||
// 监听来自iframe的消息
|
||||
window.addEventListener('message', (event) => {
|
||||
const data = event.data;
|
||||
if (data.from === 'p5.widget' && data.status === 'stop') {
|
||||
window.location.reload()
|
||||
}
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<script src="./preview-frame.bundle.js"></script>
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,585 +0,0 @@
|
||||
/*
|
||||
var vid = new Whammy.Video();
|
||||
vid.add(canvas or data url)
|
||||
vid.compile()
|
||||
*/
|
||||
|
||||
window.Whammy = (function(){
|
||||
// in this case, frames has a very specific meaning, which will be
|
||||
// detailed once i finish writing the code
|
||||
|
||||
function toWebM(frames, outputAsArray){
|
||||
var info = checkFrames(frames);
|
||||
|
||||
//max duration by cluster in milliseconds
|
||||
var CLUSTER_MAX_DURATION = 30000;
|
||||
|
||||
var EBML = [
|
||||
{
|
||||
"id": 0x1a45dfa3, // EBML
|
||||
"data": [
|
||||
{
|
||||
"data": 1,
|
||||
"id": 0x4286 // EBMLVersion
|
||||
},
|
||||
{
|
||||
"data": 1,
|
||||
"id": 0x42f7 // EBMLReadVersion
|
||||
},
|
||||
{
|
||||
"data": 4,
|
||||
"id": 0x42f2 // EBMLMaxIDLength
|
||||
},
|
||||
{
|
||||
"data": 8,
|
||||
"id": 0x42f3 // EBMLMaxSizeLength
|
||||
},
|
||||
{
|
||||
"data": "webm",
|
||||
"id": 0x4282 // DocType
|
||||
},
|
||||
{
|
||||
"data": 2,
|
||||
"id": 0x4287 // DocTypeVersion
|
||||
},
|
||||
{
|
||||
"data": 2,
|
||||
"id": 0x4285 // DocTypeReadVersion
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 0x18538067, // Segment
|
||||
"data": [
|
||||
{
|
||||
"id": 0x1549a966, // Info
|
||||
"data": [
|
||||
{
|
||||
"data": 1e6, //do things in millisecs (num of nanosecs for duration scale)
|
||||
"id": 0x2ad7b1 // TimecodeScale
|
||||
},
|
||||
{
|
||||
"data": "whammy",
|
||||
"id": 0x4d80 // MuxingApp
|
||||
},
|
||||
{
|
||||
"data": "whammy",
|
||||
"id": 0x5741 // WritingApp
|
||||
},
|
||||
{
|
||||
"data": doubleToString(info.duration),
|
||||
"id": 0x4489 // Duration
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 0x1654ae6b, // Tracks
|
||||
"data": [
|
||||
{
|
||||
"id": 0xae, // TrackEntry
|
||||
"data": [
|
||||
{
|
||||
"data": 1,
|
||||
"id": 0xd7 // TrackNumber
|
||||
},
|
||||
{
|
||||
"data": 1,
|
||||
"id": 0x73c5 // TrackUID
|
||||
},
|
||||
{
|
||||
"data": 0,
|
||||
"id": 0x9c // FlagLacing
|
||||
},
|
||||
{
|
||||
"data": "und",
|
||||
"id": 0x22b59c // Language
|
||||
},
|
||||
{
|
||||
"data": "V_VP8",
|
||||
"id": 0x86 // CodecID
|
||||
},
|
||||
{
|
||||
"data": "VP8",
|
||||
"id": 0x258688 // CodecName
|
||||
},
|
||||
{
|
||||
"data": 1,
|
||||
"id": 0x83 // TrackType
|
||||
},
|
||||
{
|
||||
"id": 0xe0, // Video
|
||||
"data": [
|
||||
{
|
||||
"data": info.width,
|
||||
"id": 0xb0 // PixelWidth
|
||||
},
|
||||
{
|
||||
"data": info.height,
|
||||
"id": 0xba // PixelHeight
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 0x1c53bb6b, // Cues
|
||||
"data": [
|
||||
//cue insertion point
|
||||
]
|
||||
}
|
||||
|
||||
//cluster insertion point
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
var segment = EBML[1];
|
||||
var cues = segment.data[2];
|
||||
|
||||
//Generate clusters (max duration)
|
||||
var frameNumber = 0;
|
||||
var clusterTimecode = 0;
|
||||
while(frameNumber < frames.length){
|
||||
|
||||
var cuePoint = {
|
||||
"id": 0xbb, // CuePoint
|
||||
"data": [
|
||||
{
|
||||
"data": Math.round(clusterTimecode),
|
||||
"id": 0xb3 // CueTime
|
||||
},
|
||||
{
|
||||
"id": 0xb7, // CueTrackPositions
|
||||
"data": [
|
||||
{
|
||||
"data": 1,
|
||||
"id": 0xf7 // CueTrack
|
||||
},
|
||||
{
|
||||
"data": 0, // to be filled in when we know it
|
||||
"size": 8,
|
||||
"id": 0xf1 // CueClusterPosition
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
cues.data.push(cuePoint);
|
||||
|
||||
var clusterFrames = [];
|
||||
var clusterDuration = 0;
|
||||
do {
|
||||
clusterFrames.push(frames[frameNumber]);
|
||||
clusterDuration += frames[frameNumber].duration;
|
||||
frameNumber++;
|
||||
}while(frameNumber < frames.length && clusterDuration < CLUSTER_MAX_DURATION);
|
||||
|
||||
var clusterCounter = 0;
|
||||
var cluster = {
|
||||
"id": 0x1f43b675, // Cluster
|
||||
"data": [
|
||||
{
|
||||
"data": Math.round(clusterTimecode),
|
||||
"id": 0xe7 // Timecode
|
||||
}
|
||||
].concat(clusterFrames.map(function(webp){
|
||||
var block = makeSimpleBlock({
|
||||
discardable: 0,
|
||||
frame: webp.data.slice(4),
|
||||
invisible: 0,
|
||||
keyframe: 1,
|
||||
lacing: 0,
|
||||
trackNum: 1,
|
||||
timecode: Math.round(clusterCounter)
|
||||
});
|
||||
clusterCounter += webp.duration;
|
||||
return {
|
||||
data: block,
|
||||
id: 0xa3
|
||||
};
|
||||
}))
|
||||
}
|
||||
|
||||
//Add cluster to segment
|
||||
segment.data.push(cluster);
|
||||
clusterTimecode += clusterDuration;
|
||||
}
|
||||
|
||||
//First pass to compute cluster positions
|
||||
var position = 0;
|
||||
for(var i = 0; i < segment.data.length; i++){
|
||||
if (i >= 3) {
|
||||
cues.data[i-3].data[1].data[1].data = position;
|
||||
}
|
||||
var data = generateEBML([segment.data[i]], outputAsArray);
|
||||
position += data.size || data.byteLength || data.length;
|
||||
if (i != 2) { // not cues
|
||||
//Save results to avoid having to encode everything twice
|
||||
segment.data[i] = data;
|
||||
}
|
||||
}
|
||||
|
||||
return generateEBML(EBML, outputAsArray)
|
||||
}
|
||||
|
||||
// sums the lengths of all the frames and gets the duration, woo
|
||||
|
||||
function checkFrames(frames){
|
||||
var width = frames[0].width,
|
||||
height = frames[0].height,
|
||||
duration = frames[0].duration;
|
||||
for(var i = 1; i < frames.length; i++){
|
||||
if(frames[i].width != width) throw "Frame " + (i + 1) + " has a different width";
|
||||
if(frames[i].height != height) throw "Frame " + (i + 1) + " has a different height";
|
||||
if(frames[i].duration < 0 || frames[i].duration > 0x7fff) throw "Frame " + (i + 1) + " has a weird duration (must be between 0 and 32767)";
|
||||
duration += frames[i].duration;
|
||||
}
|
||||
return {
|
||||
duration: duration,
|
||||
width: width,
|
||||
height: height
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
function numToBuffer(num){
|
||||
var parts = [];
|
||||
while(num > 0){
|
||||
parts.push(num & 0xff)
|
||||
num = num >> 8
|
||||
}
|
||||
return new Uint8Array(parts.reverse());
|
||||
}
|
||||
|
||||
function numToFixedBuffer(num, size){
|
||||
var parts = new Uint8Array(size);
|
||||
for(var i = size - 1; i >= 0; i--){
|
||||
parts[i] = num & 0xff;
|
||||
num = num >> 8;
|
||||
}
|
||||
return parts;
|
||||
}
|
||||
|
||||
function strToBuffer(str){
|
||||
// return new Blob([str]);
|
||||
|
||||
var arr = new Uint8Array(str.length);
|
||||
for(var i = 0; i < str.length; i++){
|
||||
arr[i] = str.charCodeAt(i)
|
||||
}
|
||||
return arr;
|
||||
// this is slower
|
||||
// return new Uint8Array(str.split('').map(function(e){
|
||||
// return e.charCodeAt(0)
|
||||
// }))
|
||||
}
|
||||
|
||||
|
||||
//sorry this is ugly, and sort of hard to understand exactly why this was done
|
||||
// at all really, but the reason is that there's some code below that i dont really
|
||||
// feel like understanding, and this is easier than using my brain.
|
||||
|
||||
function bitsToBuffer(bits){
|
||||
var data = [];
|
||||
var pad = (bits.length % 8) ? (new Array(1 + 8 - (bits.length % 8))).join('0') : '';
|
||||
bits = pad + bits;
|
||||
for(var i = 0; i < bits.length; i+= 8){
|
||||
data.push(parseInt(bits.substr(i,8),2))
|
||||
}
|
||||
return new Uint8Array(data);
|
||||
}
|
||||
|
||||
function generateEBML(json, outputAsArray){
|
||||
var ebml = [];
|
||||
for(var i = 0; i < json.length; i++){
|
||||
if (!('id' in json[i])){
|
||||
//already encoded blob or byteArray
|
||||
ebml.push(json[i]);
|
||||
continue;
|
||||
}
|
||||
|
||||
var data = json[i].data;
|
||||
if(typeof data == 'object') data = generateEBML(data, outputAsArray);
|
||||
if(typeof data == 'number') data = ('size' in json[i]) ? numToFixedBuffer(data, json[i].size) : bitsToBuffer(data.toString(2));
|
||||
if(typeof data == 'string') data = strToBuffer(data);
|
||||
|
||||
if(data.length){
|
||||
var z = z;
|
||||
}
|
||||
|
||||
/*var len = data.size || data.byteLength || data.length;
|
||||
var zeroes = Math.ceil(Math.ceil(Math.log(len)/Math.log(2))/8);
|
||||
var size_str = len.toString(2);
|
||||
var padded = (new Array((zeroes * 7 + 7 + 1) - size_str.length)).join('0') + size_str;
|
||||
var size = (new Array(zeroes)).join('0') + '1' + padded;*/
|
||||
|
||||
var len = data.size || data.byteLength || data.length;
|
||||
var zeroes = 0
|
||||
for( var j = 56; j > 0; j-= 7 ) {
|
||||
if( len > Math.pow( 2, j ) - 2 ) {
|
||||
zeroes = j / 7
|
||||
break
|
||||
}
|
||||
}
|
||||
var size_str = len.toString(2);
|
||||
var base = ( new Array( 8 * ( zeroes + 1 ) + 1 ) ).join( '0' )
|
||||
var pre = ( new Array( zeroes + 1 ) ).join( '0' ) + 1;
|
||||
var padded = base.substr( 0, base.length - size_str.length - pre.length ) + size_str;
|
||||
var size = pre + padded;
|
||||
//i actually dont quite understand what went on up there, so I'm not really
|
||||
//going to fix this, i'm probably just going to write some hacky thing which
|
||||
//converts that string into a buffer-esque thing
|
||||
|
||||
ebml.push(numToBuffer(json[i].id));
|
||||
ebml.push(bitsToBuffer(size));
|
||||
ebml.push(data)
|
||||
|
||||
|
||||
}
|
||||
|
||||
//output as blob or byteArray
|
||||
if(outputAsArray){
|
||||
//convert ebml to an array
|
||||
var buffer = toFlatArray(ebml)
|
||||
return new Uint8Array(buffer);
|
||||
}else{
|
||||
return new Blob(ebml, {type: "video/webm"});
|
||||
}
|
||||
}
|
||||
|
||||
function toFlatArray(arr, outBuffer){
|
||||
if(outBuffer == null){
|
||||
outBuffer = [];
|
||||
}
|
||||
for(var i = 0; i < arr.length; i++){
|
||||
if(typeof arr[i] == 'object'){
|
||||
//an array
|
||||
toFlatArray(arr[i], outBuffer)
|
||||
}else{
|
||||
//a simple element
|
||||
outBuffer.push(arr[i]);
|
||||
}
|
||||
}
|
||||
return outBuffer;
|
||||
}
|
||||
|
||||
//OKAY, so the following two functions are the string-based old stuff, the reason they're
|
||||
//still sort of in here, is that they're actually faster than the new blob stuff because
|
||||
//getAsFile isn't widely implemented, or at least, it doesn't work in chrome, which is the
|
||||
// only browser which supports get as webp
|
||||
|
||||
//Converting between a string of 0010101001's and binary back and forth is probably inefficient
|
||||
//TODO: get rid of this function
|
||||
function toBinStr_old(bits){
|
||||
var data = '';
|
||||
var pad = (bits.length % 8) ? (new Array(1 + 8 - (bits.length % 8))).join('0') : '';
|
||||
bits = pad + bits;
|
||||
for(var i = 0; i < bits.length; i+= 8){
|
||||
data += String.fromCharCode(parseInt(bits.substr(i,8),2))
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
function generateEBML_old(json){
|
||||
var ebml = '';
|
||||
for(var i = 0; i < json.length; i++){
|
||||
var data = json[i].data;
|
||||
if(typeof data == 'object') data = generateEBML_old(data);
|
||||
if(typeof data == 'number') data = toBinStr_old(data.toString(2));
|
||||
|
||||
var len = data.length;
|
||||
var zeroes = Math.ceil(Math.ceil(Math.log(len)/Math.log(2))/8);
|
||||
var size_str = len.toString(2);
|
||||
var padded = (new Array((zeroes * 7 + 7 + 1) - size_str.length)).join('0') + size_str;
|
||||
var size = (new Array(zeroes)).join('0') + '1' + padded;
|
||||
|
||||
ebml += toBinStr_old(json[i].id.toString(2)) + toBinStr_old(size) + data;
|
||||
|
||||
}
|
||||
return ebml;
|
||||
}
|
||||
|
||||
//woot, a function that's actually written for this project!
|
||||
//this parses some json markup and makes it into that binary magic
|
||||
//which can then get shoved into the matroska comtainer (peaceably)
|
||||
|
||||
function makeSimpleBlock(data){
|
||||
var flags = 0;
|
||||
if (data.keyframe) flags |= 128;
|
||||
if (data.invisible) flags |= 8;
|
||||
if (data.lacing) flags |= (data.lacing << 1);
|
||||
if (data.discardable) flags |= 1;
|
||||
if (data.trackNum > 127) {
|
||||
throw "TrackNumber > 127 not supported";
|
||||
}
|
||||
var out = [data.trackNum | 0x80, data.timecode >> 8, data.timecode & 0xff, flags].map(function(e){
|
||||
return String.fromCharCode(e)
|
||||
}).join('') + data.frame;
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// here's something else taken verbatim from weppy, awesome rite?
|
||||
|
||||
function parseWebP(riff){
|
||||
var VP8 = riff.RIFF[0].WEBP[0];
|
||||
|
||||
var frame_start = VP8.indexOf('\x9d\x01\x2a'); //A VP8 keyframe starts with the 0x9d012a header
|
||||
for(var i = 0, c = []; i < 4; i++) c[i] = VP8.charCodeAt(frame_start + 3 + i);
|
||||
|
||||
var width, horizontal_scale, height, vertical_scale, tmp;
|
||||
|
||||
//the code below is literally copied verbatim from the bitstream spec
|
||||
tmp = (c[1] << 8) | c[0];
|
||||
width = tmp & 0x3FFF;
|
||||
horizontal_scale = tmp >> 14;
|
||||
tmp = (c[3] << 8) | c[2];
|
||||
height = tmp & 0x3FFF;
|
||||
vertical_scale = tmp >> 14;
|
||||
return {
|
||||
width: width,
|
||||
height: height,
|
||||
data: VP8,
|
||||
riff: riff
|
||||
}
|
||||
}
|
||||
|
||||
// i think i'm going off on a riff by pretending this is some known
|
||||
// idiom which i'm making a casual and brilliant pun about, but since
|
||||
// i can't find anything on google which conforms to this idiomatic
|
||||
// usage, I'm assuming this is just a consequence of some psychotic
|
||||
// break which makes me make up puns. well, enough riff-raff (aha a
|
||||
// rescue of sorts), this function was ripped wholesale from weppy
|
||||
|
||||
function parseRIFF(string){
|
||||
var offset = 0;
|
||||
var chunks = {};
|
||||
|
||||
while (offset < string.length) {
|
||||
var id = string.substr(offset, 4);
|
||||
chunks[id] = chunks[id] || [];
|
||||
if (id == 'RIFF' || id == 'LIST') {
|
||||
var len = parseInt(string.substr(offset + 4, 4).split('').map(function(i){
|
||||
var unpadded = i.charCodeAt(0).toString(2);
|
||||
return (new Array(8 - unpadded.length + 1)).join('0') + unpadded
|
||||
}).join(''),2);
|
||||
var data = string.substr(offset + 4 + 4, len);
|
||||
offset += 4 + 4 + len;
|
||||
chunks[id].push(parseRIFF(data));
|
||||
} else if (id == 'WEBP') {
|
||||
// Use (offset + 8) to skip past "VP8 "/"VP8L"/"VP8X" field after "WEBP"
|
||||
chunks[id].push(string.substr(offset + 8));
|
||||
offset = string.length;
|
||||
} else {
|
||||
// Unknown chunk type; push entire payload
|
||||
chunks[id].push(string.substr(offset + 4));
|
||||
offset = string.length;
|
||||
}
|
||||
}
|
||||
return chunks;
|
||||
}
|
||||
|
||||
// here's a little utility function that acts as a utility for other functions
|
||||
// basically, the only purpose is for encoding "Duration", which is encoded as
|
||||
// a double (considerably more difficult to encode than an integer)
|
||||
function doubleToString(num){
|
||||
return [].slice.call(
|
||||
new Uint8Array(
|
||||
(
|
||||
new Float64Array([num]) //create a float64 array
|
||||
).buffer) //extract the array buffer
|
||||
, 0) // convert the Uint8Array into a regular array
|
||||
.map(function(e){ //since it's a regular array, we can now use map
|
||||
return String.fromCharCode(e) // encode all the bytes individually
|
||||
})
|
||||
.reverse() //correct the byte endianness (assume it's little endian for now)
|
||||
.join('') // join the bytes in holy matrimony as a string
|
||||
}
|
||||
|
||||
function WhammyVideo(speed, quality){ // a more abstract-ish API
|
||||
this.frames = [];
|
||||
this.duration = 1000 / speed;
|
||||
this.quality = quality || 0.8;
|
||||
}
|
||||
|
||||
WhammyVideo.prototype.add = function(frame, duration){
|
||||
if(typeof duration != 'undefined' && this.duration) throw "you can't pass a duration if the fps is set";
|
||||
if(typeof duration == 'undefined' && !this.duration) throw "if you don't have the fps set, you need to have durations here.";
|
||||
if(frame.canvas){ //CanvasRenderingContext2D
|
||||
frame = frame.canvas;
|
||||
}
|
||||
if(frame.toDataURL){
|
||||
// frame = frame.toDataURL('image/webp', this.quality);
|
||||
// quickly store image data so we don't block cpu. encode in compile method.
|
||||
frame = frame.getContext('2d').getImageData(0, 0, frame.width, frame.height);
|
||||
}else if(typeof frame != "string"){
|
||||
throw "frame must be a a HTMLCanvasElement, a CanvasRenderingContext2D or a DataURI formatted string"
|
||||
}
|
||||
if (typeof frame === "string" && !(/^data:image\/webp;base64,/ig).test(frame)) {
|
||||
throw "Input must be formatted properly as a base64 encoded DataURI of type image/webp";
|
||||
}
|
||||
this.frames.push({
|
||||
image: frame,
|
||||
duration: duration || this.duration
|
||||
});
|
||||
};
|
||||
|
||||
// deferred webp encoding. Draws image data to canvas, then encodes as dataUrl
|
||||
WhammyVideo.prototype.encodeFrames = function(callback){
|
||||
|
||||
if(this.frames[0].image instanceof ImageData){
|
||||
|
||||
var frames = this.frames;
|
||||
var tmpCanvas = document.createElement('canvas');
|
||||
var tmpContext = tmpCanvas.getContext('2d');
|
||||
tmpCanvas.width = this.frames[0].image.width;
|
||||
tmpCanvas.height = this.frames[0].image.height;
|
||||
|
||||
var encodeFrame = function(index){
|
||||
var frame = frames[index];
|
||||
tmpContext.putImageData(frame.image, 0, 0);
|
||||
frame.image = tmpCanvas.toDataURL('image/webp', this.quality);
|
||||
if(index < frames.length-1){
|
||||
setTimeout(function(){ encodeFrame(index + 1); }, 1);
|
||||
}else{
|
||||
callback();
|
||||
}
|
||||
}.bind(this);
|
||||
|
||||
encodeFrame(0);
|
||||
}else{
|
||||
callback();
|
||||
}
|
||||
};
|
||||
|
||||
WhammyVideo.prototype.compile = function(outputAsArray, callback){
|
||||
|
||||
this.encodeFrames(function(){
|
||||
|
||||
var webm = new toWebM(this.frames.map(function(frame){
|
||||
var webp = parseWebP(parseRIFF(atob(frame.image.slice(23))));
|
||||
webp.duration = frame.duration;
|
||||
return webp;
|
||||
}), outputAsArray);
|
||||
callback(webm);
|
||||
|
||||
}.bind(this));
|
||||
};
|
||||
|
||||
return {
|
||||
Video: WhammyVideo,
|
||||
fromImageArray: function(images, fps, outputAsArray){
|
||||
return toWebM(images.map(function(image){
|
||||
var webp = parseWebP(parseRIFF(atob(image.slice(23))))
|
||||
webp.duration = 1000 / fps;
|
||||
return webp;
|
||||
}), outputAsArray)
|
||||
},
|
||||
toWebM: toWebM
|
||||
// expose methods of madness
|
||||
}
|
||||
})()
|
||||
@@ -1,334 +0,0 @@
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var lookup = [
|
||||
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
|
||||
'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
|
||||
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
|
||||
'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f',
|
||||
'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n',
|
||||
'o', 'p', 'q', 'r', 's', 't', 'u', 'v',
|
||||
'w', 'x', 'y', 'z', '0', '1', '2', '3',
|
||||
'4', '5', '6', '7', '8', '9', '+', '/'
|
||||
];
|
||||
function clean(length) {
|
||||
var i, buffer = new Uint8Array(length);
|
||||
for (i = 0; i < length; i += 1) {
|
||||
buffer[i] = 0;
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
function extend(orig, length, addLength, multipleOf) {
|
||||
var newSize = length + addLength,
|
||||
buffer = clean((parseInt(newSize / multipleOf) + 1) * multipleOf);
|
||||
|
||||
buffer.set(orig);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
function pad(num, bytes, base) {
|
||||
num = num.toString(base || 8);
|
||||
return "000000000000".substr(num.length + 12 - bytes) + num;
|
||||
}
|
||||
|
||||
function stringToUint8 (input, out, offset) {
|
||||
var i, length;
|
||||
|
||||
out = out || clean(input.length);
|
||||
|
||||
offset = offset || 0;
|
||||
for (i = 0, length = input.length; i < length; i += 1) {
|
||||
out[offset] = input.charCodeAt(i);
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
function uint8ToBase64(uint8) {
|
||||
var i,
|
||||
extraBytes = uint8.length % 3, // if we have 1 byte left, pad 2 bytes
|
||||
output = "",
|
||||
temp, length;
|
||||
|
||||
function tripletToBase64 (num) {
|
||||
return lookup[num >> 18 & 0x3F] + lookup[num >> 12 & 0x3F] + lookup[num >> 6 & 0x3F] + lookup[num & 0x3F];
|
||||
};
|
||||
|
||||
// go through the array every three bytes, we'll deal with trailing stuff later
|
||||
for (i = 0, length = uint8.length - extraBytes; i < length; i += 3) {
|
||||
temp = (uint8[i] << 16) + (uint8[i + 1] << 8) + (uint8[i + 2]);
|
||||
output += tripletToBase64(temp);
|
||||
}
|
||||
|
||||
// this prevents an ERR_INVALID_URL in Chrome (Firefox okay)
|
||||
switch (output.length % 4) {
|
||||
case 1:
|
||||
output += '=';
|
||||
break;
|
||||
case 2:
|
||||
output += '==';
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return output;
|
||||
}
|
||||
|
||||
window.utils = {}
|
||||
window.utils.clean = clean;
|
||||
window.utils.pad = pad;
|
||||
window.utils.extend = extend;
|
||||
window.utils.stringToUint8 = stringToUint8;
|
||||
window.utils.uint8ToBase64 = uint8ToBase64;
|
||||
}());
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
/*
|
||||
struct posix_header { // byte offset
|
||||
char name[100]; // 0
|
||||
char mode[8]; // 100
|
||||
char uid[8]; // 108
|
||||
char gid[8]; // 116
|
||||
char size[12]; // 124
|
||||
char mtime[12]; // 136
|
||||
char chksum[8]; // 148
|
||||
char typeflag; // 156
|
||||
char linkname[100]; // 157
|
||||
char magic[6]; // 257
|
||||
char version[2]; // 263
|
||||
char uname[32]; // 265
|
||||
char gname[32]; // 297
|
||||
char devmajor[8]; // 329
|
||||
char devminor[8]; // 337
|
||||
char prefix[155]; // 345
|
||||
// 500
|
||||
};
|
||||
*/
|
||||
|
||||
var utils = window.utils,
|
||||
headerFormat;
|
||||
|
||||
headerFormat = [
|
||||
{
|
||||
'field': 'fileName',
|
||||
'length': 100
|
||||
},
|
||||
{
|
||||
'field': 'fileMode',
|
||||
'length': 8
|
||||
},
|
||||
{
|
||||
'field': 'uid',
|
||||
'length': 8
|
||||
},
|
||||
{
|
||||
'field': 'gid',
|
||||
'length': 8
|
||||
},
|
||||
{
|
||||
'field': 'fileSize',
|
||||
'length': 12
|
||||
},
|
||||
{
|
||||
'field': 'mtime',
|
||||
'length': 12
|
||||
},
|
||||
{
|
||||
'field': 'checksum',
|
||||
'length': 8
|
||||
},
|
||||
{
|
||||
'field': 'type',
|
||||
'length': 1
|
||||
},
|
||||
{
|
||||
'field': 'linkName',
|
||||
'length': 100
|
||||
},
|
||||
{
|
||||
'field': 'ustar',
|
||||
'length': 8
|
||||
},
|
||||
{
|
||||
'field': 'owner',
|
||||
'length': 32
|
||||
},
|
||||
{
|
||||
'field': 'group',
|
||||
'length': 32
|
||||
},
|
||||
{
|
||||
'field': 'majorNumber',
|
||||
'length': 8
|
||||
},
|
||||
{
|
||||
'field': 'minorNumber',
|
||||
'length': 8
|
||||
},
|
||||
{
|
||||
'field': 'filenamePrefix',
|
||||
'length': 155
|
||||
},
|
||||
{
|
||||
'field': 'padding',
|
||||
'length': 12
|
||||
}
|
||||
];
|
||||
|
||||
function formatHeader(data, cb) {
|
||||
var buffer = utils.clean(512),
|
||||
offset = 0;
|
||||
|
||||
headerFormat.forEach(function (value) {
|
||||
var str = data[value.field] || "",
|
||||
i, length;
|
||||
|
||||
for (i = 0, length = str.length; i < length; i += 1) {
|
||||
buffer[offset] = str.charCodeAt(i);
|
||||
offset += 1;
|
||||
}
|
||||
|
||||
offset += value.length - i; // space it out with nulls
|
||||
});
|
||||
|
||||
if (typeof cb === 'function') {
|
||||
return cb(buffer, offset);
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
window.header = {}
|
||||
window.header.structure = headerFormat;
|
||||
window.header.format = formatHeader;
|
||||
}());
|
||||
|
||||
(function () {
|
||||
"use strict";
|
||||
|
||||
var header = window.header,
|
||||
utils = window.utils,
|
||||
recordSize = 512,
|
||||
blockSize;
|
||||
|
||||
function Tar(recordsPerBlock) {
|
||||
this.written = 0;
|
||||
blockSize = (recordsPerBlock || 20) * recordSize;
|
||||
this.out = utils.clean(blockSize);
|
||||
this.blocks = [];
|
||||
this.length = 0;
|
||||
}
|
||||
|
||||
Tar.prototype.append = function (filepath, input, opts, callback) {
|
||||
var data,
|
||||
checksum,
|
||||
mode,
|
||||
mtime,
|
||||
uid,
|
||||
gid,
|
||||
headerArr;
|
||||
|
||||
if (typeof input === 'string') {
|
||||
input = utils.stringToUint8(input);
|
||||
} else if (input.constructor !== Uint8Array.prototype.constructor) {
|
||||
throw 'Invalid input type. You gave me: ' + input.constructor.toString().match(/function\s*([$A-Za-z_][0-9A-Za-z_]*)\s*\(/)[1];
|
||||
}
|
||||
|
||||
if (typeof opts === 'function') {
|
||||
callback = opts;
|
||||
opts = {};
|
||||
}
|
||||
|
||||
opts = opts || {};
|
||||
|
||||
mode = opts.mode || parseInt('777', 8) & 0xfff;
|
||||
mtime = opts.mtime || Math.floor(+new Date() / 1000);
|
||||
uid = opts.uid || 0;
|
||||
gid = opts.gid || 0;
|
||||
|
||||
data = {
|
||||
fileName: filepath,
|
||||
fileMode: utils.pad(mode, 7),
|
||||
uid: utils.pad(uid, 7),
|
||||
gid: utils.pad(gid, 7),
|
||||
fileSize: utils.pad(input.length, 11),
|
||||
mtime: utils.pad(mtime, 11),
|
||||
checksum: ' ',
|
||||
type: '0', // just a file
|
||||
ustar: 'ustar ',
|
||||
owner: opts.owner || '',
|
||||
group: opts.group || ''
|
||||
};
|
||||
|
||||
// calculate the checksum
|
||||
checksum = 0;
|
||||
Object.keys(data).forEach(function (key) {
|
||||
var i, value = data[key], length;
|
||||
|
||||
for (i = 0, length = value.length; i < length; i += 1) {
|
||||
checksum += value.charCodeAt(i);
|
||||
}
|
||||
});
|
||||
|
||||
data.checksum = utils.pad(checksum, 6) + "\u0000 ";
|
||||
|
||||
headerArr = header.format(data);
|
||||
|
||||
var headerLength = Math.ceil( headerArr.length / recordSize ) * recordSize;
|
||||
var inputLength = Math.ceil( input.length / recordSize ) * recordSize;
|
||||
|
||||
this.blocks.push( { header: headerArr, input: input, headerLength: headerLength, inputLength: inputLength } );
|
||||
|
||||
};
|
||||
|
||||
Tar.prototype.save = function() {
|
||||
|
||||
var buffers = [];
|
||||
var chunks = [];
|
||||
var length = 0;
|
||||
var max = Math.pow( 2, 20 );
|
||||
|
||||
var chunk = [];
|
||||
this.blocks.forEach( function( b ) {
|
||||
if( length + b.headerLength + b.inputLength > max ) {
|
||||
chunks.push( { blocks: chunk, length: length } );
|
||||
chunk = [];
|
||||
length = 0;
|
||||
}
|
||||
chunk.push( b );
|
||||
length += b.headerLength + b.inputLength;
|
||||
} );
|
||||
chunks.push( { blocks: chunk, length: length } );
|
||||
|
||||
chunks.forEach( function( c ) {
|
||||
|
||||
var buffer = new Uint8Array( c.length );
|
||||
var written = 0;
|
||||
c.blocks.forEach( function( b ) {
|
||||
buffer.set( b.header, written );
|
||||
written += b.headerLength;
|
||||
buffer.set( b.input, written );
|
||||
written += b.inputLength;
|
||||
} );
|
||||
buffers.push( buffer );
|
||||
|
||||
} );
|
||||
|
||||
buffers.push( new Uint8Array( 2 * recordSize ) );
|
||||
|
||||
return new Blob( buffers, { type: 'octet/stream' } );
|
||||
|
||||
};
|
||||
|
||||
Tar.prototype.clear = function () {
|
||||
this.written = 0;
|
||||
this.out = utils.clean(blockSize);
|
||||
};
|
||||
|
||||
window.Tar = Tar;
|
||||
}());
|
||||
File diff suppressed because it is too large
Load Diff
@@ -65,6 +65,7 @@
|
||||
"use strict";
|
||||
var esprima = __webpack_require__(204);
|
||||
var parse = esprima.parse;
|
||||
var LOOP_CHECK_FUNC_NAME = '__loopCheck';
|
||||
function default_1(src, opts, fn) {
|
||||
if (typeof opts === 'function') {
|
||||
fn = opts;
|
||||
@@ -147,7 +148,6 @@
|
||||
console.log('#Found draw function:', node);
|
||||
var hasCapturerStart_1 = false;
|
||||
var hasCapturerEnd_1 = false;
|
||||
// Traverse the function body to check for `capturer_start()` and `capturer_end()`
|
||||
(function checkCapturerCalls(bodyNode) {
|
||||
if (bodyNode.type === 'CallExpression' &&
|
||||
bodyNode.callee &&
|
||||
@@ -5934,25 +5934,48 @@
|
||||
"use strict";
|
||||
function defaultInserter(name) {
|
||||
return function (node) {
|
||||
return name + "();";
|
||||
return name + '();';
|
||||
};
|
||||
}
|
||||
var LOOP_CHECK_FUNC_NAME = '__loopCheck';
|
||||
function LoopInserter(fn) {
|
||||
if (typeof (fn) == "string")
|
||||
fn = defaultInserter(fn);
|
||||
return function (node) {
|
||||
if (node.type == 'WhileStatement' ||
|
||||
node.type == 'ForStatement' ||
|
||||
node.type == 'DoWhileStatement') {
|
||||
node.body.update('{ ' + fn(node) + node.body.source() + ' }');
|
||||
return true;
|
||||
if (node.type === 'ForStatement' ||
|
||||
node.type === 'WhileStatement' ||
|
||||
node.type === 'DoWhileStatement') {
|
||||
var loopCheckCode = {
|
||||
type: 'ExpressionStatement',
|
||||
expression: {
|
||||
type: 'CallExpression',
|
||||
callee: {
|
||||
type: 'Identifier',
|
||||
name: LOOP_CHECK_FUNC_NAME
|
||||
},
|
||||
arguments: [
|
||||
{
|
||||
type: 'Literal',
|
||||
value: JSON.stringify(node.range)
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
if (node.body.type === 'BlockStatement') {
|
||||
// Insert the loop check at the beginning of the block
|
||||
node.body.body.unshift(loopCheckCode);
|
||||
}
|
||||
else {
|
||||
// Wrap the single statement body in a block and insert the loop check
|
||||
node.body = {
|
||||
type: 'BlockStatement',
|
||||
body: [loopCheckCode, node.body]
|
||||
};
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return '';
|
||||
};
|
||||
}
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.default = LoopInserter;
|
||||
;
|
||||
|
||||
|
||||
/***/ }),
|
||||
|
||||
File diff suppressed because one or more lines are too long
+65
-32
@@ -5,8 +5,6 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Document</title>
|
||||
|
||||
|
||||
<style>
|
||||
body {
|
||||
height: 600px;
|
||||
@@ -38,14 +36,6 @@
|
||||
/* 滚动条滑块悬停颜色 */
|
||||
}
|
||||
</style>
|
||||
<!-- <script src="./p5.js"></script> -->
|
||||
<!-- <script src="./sketch.js"></script> -->
|
||||
<!-- <script src="./src/CCapture.js"></script> -->
|
||||
<!-- <script src="./src/gif.js"></script>
|
||||
<script src="./src/gif.worker.js"></script> -->
|
||||
<!-- <script src="./src/download.js"></script> -->
|
||||
<!-- <script src="./src/webm-writer-0.2.0.js"></script> -->
|
||||
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -57,14 +47,12 @@
|
||||
return urlParams.get('id');
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 监听来自iframe的消息
|
||||
window.addEventListener('message', (event) => {
|
||||
const data = event.data;
|
||||
const nodeId = getIdFromUrl(window.location.href);
|
||||
|
||||
if (data.from === 'p5.widget' && data.status === 'save') {
|
||||
// console.log('#p5html', data)
|
||||
if (data.from === 'p5.widget' && data.status === 'save' && data._from == 'main') {
|
||||
const frames = data.frames;
|
||||
// 示例用法
|
||||
// const url = 'https://example.com/page?id=12345';
|
||||
@@ -72,32 +60,77 @@
|
||||
frames,
|
||||
from: 'p5.widget',
|
||||
status: 'save',
|
||||
nodeId,
|
||||
id:(new Date()).getTime()
|
||||
}, '*');
|
||||
// window.location.reload()
|
||||
}
|
||||
if (data.from === 'p5.widget' && data.status === 'stop') {
|
||||
|
||||
window.parent.postMessage({
|
||||
from: 'p5.widget',
|
||||
status: 'stop',
|
||||
nodeId
|
||||
}, '*');
|
||||
window.location.reload()
|
||||
// window.location.reload()
|
||||
}
|
||||
// console.log(data)
|
||||
// if (data.from === 'p5.widget' && data.status === 'capture') {
|
||||
// window.parent.postMessage({
|
||||
// from: 'p5.widget',
|
||||
// status: 'capture',
|
||||
// frameCount: data.frameCount,
|
||||
// maxCount: data.maxCount,
|
||||
// nodeId
|
||||
// }, '*');
|
||||
// }
|
||||
|
||||
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
<script type="text/p5" data-height="500" data-preview-width="300">
|
||||
function setup() {
|
||||
createCanvas(100, 100);
|
||||
}
|
||||
<script type="text/p5" data-height="500" data-preview-width="300" >
|
||||
const TO_GOAL = 0.3
|
||||
const NOISE_AMP = 3.1415 / 100
|
||||
|
||||
let g_width
|
||||
let g_red
|
||||
let g_green
|
||||
let g_blue
|
||||
|
||||
function draw_triangle (angle) {
|
||||
triangle(
|
||||
cos(TWO_PI / 3 + angle) * g_width,
|
||||
sin(TWO_PI / 3 + angle) * g_width,
|
||||
cos((TWO_PI / 3) * 2 + angle) * g_width,
|
||||
sin((TWO_PI / 3) * 2 + angle) * g_width,
|
||||
cos((TWO_PI / 3) * 3 + angle) * g_width,
|
||||
sin((TWO_PI / 3) * 3 + angle) * g_width
|
||||
)
|
||||
}
|
||||
|
||||
function setup () {
|
||||
createCanvas(300, 300)
|
||||
mouseX = 300 / 2
|
||||
mouseY = 300 / 2
|
||||
g_red = map(mouseX, 0, 300, 0, PI)
|
||||
g_green = map(mouseX, 0, 300, 0, PI)
|
||||
g_blue = map(mouseX, 0, 300, 0, PI)
|
||||
g_width = min(300, 300) / 3
|
||||
}
|
||||
|
||||
function draw () {
|
||||
background(0)
|
||||
push()
|
||||
translate(300 / 2, 300 / 2)
|
||||
blendMode(ADD)
|
||||
g_red =
|
||||
g_red +
|
||||
(map(0, 0, windowWidth, 0, PI) - g_red) * TO_GOAL +
|
||||
random(-NOISE_AMP, NOISE_AMP)
|
||||
g_green =
|
||||
g_green + (g_red - g_green) * TO_GOAL + random(-NOISE_AMP, NOISE_AMP)
|
||||
g_blue = g_blue + (g_green - g_blue) * TO_GOAL + random(-NOISE_AMP, NOISE_AMP)
|
||||
fill(255, 0, 0)
|
||||
draw_triangle(g_red)
|
||||
fill(0, 255, 0)
|
||||
draw_triangle(g_green)
|
||||
fill(0, 0, 255)
|
||||
draw_triangle(g_blue)
|
||||
pop()
|
||||
}
|
||||
|
||||
function draw() {
|
||||
background(255, 0, 200);
|
||||
}
|
||||
</script>
|
||||
<script src="./p5-widget/p5-widget.js"></script>
|
||||
</body>
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
dialog {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
max-width: 20em;
|
||||
color: white;
|
||||
background-color: #000;
|
||||
border: none;
|
||||
position: relative;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
#progress-container {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
}
|
||||
|
||||
progress {
|
||||
width: 100%;
|
||||
height: 1em;
|
||||
border: none;
|
||||
background-color: #fff;
|
||||
color: #eee;
|
||||
}
|
||||
|
||||
progress::-webkit-progress-bar {
|
||||
background-color: #333;
|
||||
}
|
||||
|
||||
progress::-webkit-progress-value {
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
progress::-moz-progress-bar {
|
||||
background-color: #eee;
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
body {
|
||||
margin: 0;
|
||||
background-color: #000;
|
||||
color: #fff;
|
||||
font-family: Monospace;
|
||||
font-size: 13px;
|
||||
line-height: 24px;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #ff0;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
#info {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
box-sizing: border-box;
|
||||
text-align: center;
|
||||
-moz-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
pointer-events: none;
|
||||
z-index: 1; /* TODO Solve this in HTML */
|
||||
}
|
||||
|
||||
a, button, input, select {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.lil-gui {
|
||||
z-index: 2 !important; /* TODO Solve this in HTML */
|
||||
}
|
||||
|
||||
@media all and ( max-width: 640px ) {
|
||||
.lil-gui.root {
|
||||
right: auto;
|
||||
top: auto;
|
||||
max-height: 50%;
|
||||
max-width: 80%;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
#overlay {
|
||||
position: absolute;
|
||||
font-size: 16px;
|
||||
z-index: 2;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
background: rgba(0,0,0,0.7);
|
||||
}
|
||||
|
||||
#overlay button {
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border: 1px solid rgb(255, 255, 255);
|
||||
border-radius: 4px;
|
||||
color: #ffffff;
|
||||
padding: 12px 18px;
|
||||
text-transform: uppercase;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#notSupported {
|
||||
width: 50%;
|
||||
margin: auto;
|
||||
background-color: #f00;
|
||||
margin-top: 20px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#screenshotButton {
|
||||
position: absolute;
|
||||
bottom: 10px; /* Adjust as needed */
|
||||
left: 10px; /* Position to the left */
|
||||
z-index: 10; /* Ensure this is above the canvas's z-index */
|
||||
padding: 5px 5px;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
background: linear-gradient(145deg, #007bff, #0056b3);
|
||||
color: #ffffff;
|
||||
font-size: 10px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
||||
transition: background 0.3s ease-in-out, transform 0.2s ease;
|
||||
}
|
||||
|
||||
#screenshotButton:hover {
|
||||
background: linear-gradient(145deg, #0056b3, #007bff);
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
#screenshotButton:active {
|
||||
background: #0056b3;
|
||||
transform: translateY(1px);
|
||||
box-shadow: 0 3px 5px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
|
||||
<link type="text/css" rel="stylesheet" href="/extensions/comfyui-mixlab-nodes/style/threeStyle.css">
|
||||
<link type="text/css" rel="stylesheet" href="/extensions/comfyui-mixlab-nodes/style/progressStyle.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="progress-container">
|
||||
<dialog open id="progress-dialog">
|
||||
<p>
|
||||
<label for="progress-indicator">Loading scene...</label>
|
||||
</p>
|
||||
<progress max="100" id="progress-indicator"></progress>
|
||||
</dialog>
|
||||
</div>
|
||||
|
||||
<div id="container"></div>
|
||||
|
||||
<script id="visualizer" type="module" filepath="" crossorigin src="/extensions/comfyui-mixlab-nodes/lib/threeVisualizer.js"></script>
|
||||
<button id="screenshotButton">Take Screenshot</button>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user