将 HTML 画布导出为图像序列

发布于 2024-12-27 15:34:16 字数 3775 浏览 1 评论 0原文

我创建了一个非常简单的页面,它将采用 Three.js 场景(尽管这可以是任何非 WebGL 动画)并导出单个图像(或图像序列)然后转换为视频。

我这样做的部分原因是为了学习 Python,但能够在 Three.js 中快速制作原型,然后导出大量高分辨率丝滑视频的想法对我来说非常有吸引力。过去,我使用屏幕捕获软件来抓取视频,但总感觉有点笨拙,而且最终视频中也会显示出 FPS 的实时下降。

我目前的流程如下:

Javascript

  • 创建 WebGL 场景并设置渲染周期
  • 将画布宽度/高度设置为所需尺寸
  • 使用 requestAnimationFrame 渲染场景
  • 暂停场景的渲染/更新周期
  • 在canvas元素上调用toDataURL()并检索
  • 对python脚本的base64字符串POST请求,传递base64字符串(以及其他内容,例如要保存到的目标目录以及是否单幅图像或序列正在被捕获)

Python:

  • 剥离 MIME 标头内容类型并解码 base64 字符串
  • 将字符串写入图像文件
  • 如果文件已写入,则打印/返回表示成功状态的字符串,否则打印出有问题的错误消息

import base64, cgi, cgitb, datetime, glob, re, os

cgitb.enable() #cgitb.enable(display=0, logdir='/tmp') print "Content-type: text/html" print def main(): form = cgi.FieldStorage() saveLocation = "../httpdocs/export/" # POST variables dataURL = form['dataURL'].value captureSequence = form['captureSequence'].value folderName = saveLocation + form['folderName'].value saveImage(dataURL, captureSequence, saveLocation, folderName) def saveImage(dataURL, captureSequence, saveLocation, folderName): # strip out MIME content-type (e.g. "data:image/png;base64,") dataURL = dataURL[dataURL.index(','):] decodedString = base64.decodestring(dataURL) if captureSequence == 'true': # based off http://www.akeric.com/blog/?p=632 currentImages = glob.glob(folderName + "/*.jpg") # TODO: perhaps filenames shouldnt start at %08d+1 but rather %08d+0? numList = [0] if not os.path.exists(folderName): os.makedirs(folderName) for img in currentImages: i = os.path.splitext(img)[0] try: num = re.findall('[0-9]+$', i)[0] numList.append(int(num)) except IndexError: pass numList = sorted(numList) newNum = numList[-1] + 1 saveName = folderName + '/%08d.jpg' % newNum else: if not os.path.exists(saveLocation): os.makedirs(saveLocation) saveName = saveLocation + datetime.datetime.now().isoformat().replace(':', '.') + '.jpg' # TODO; rather than returning a simple string, consider returning an object? try: output = open(saveName, 'w') output.write(decodedString) output.close() print 'true' except Exception, e: print e if __name__ == '__main__': main()

Javascript

  • 接收来自Python脚本的响应并显示返回的消息
  • 恢复/更新渲染周期
  • (根据需要重复处理尽可能多的帧)

这是我总是在本地运行的东西,因此不会有冲突的风险写或类似的东西。

我已经做了一些快速测试,它似乎在大多数情况下都能工作,尽管有点慢。


  • 我是否遗漏了一些完全明显的事情?如何改进? (尤其在Python方面......)
  • 对每个图像进行单独的ajax调用是否效率低下?一个优点是我可以随时停止/关闭选项卡,并且它将保存到那时为止的所有图像。存储所有这些 base64 字符串并在最后发送它们会有什么好处吗?
  • 由于 requestAnimationFrame 将更新周期限制为 60fps,是否可以轻松设置较低的帧速率?假设出于某种风格原因,我想以 15fps 更新所有内容,唯一的选择是使用 setTimeout(callback, 1000 / targetFPS) 并理解它会 随时间漂移
  • 接着上面的内容,这个动画有一个 var frame,它在每个更新周期都会增加 1。然后,该变量用于控制动画的各个部分(例如,旋转立方体,并传递到顶点/片段着色器以操纵颜色和纹理 UV 坐标)。

    如果我想模拟 15 fps 之类的东西,我需要将 frame 增加 (60 / 15) 是否正确?有没有更优雅的方法可以轻松地在上限帧速率之间切换?

  • 最后,有没有什么技术可以用来提高渲染图像的质量? (大声思考,以双倍大小保存然后减少它们?)

我真的希望这是有道理的,任何见解或建议都将受到极大的赞赏。

源文件: http://cl.ly/3V46120C2d3B0A1o3o25(在 Mac / Chrome 上测试)稳定,需要 WebGL)

I've created a very naive page that will take a Three.js scene (though this could be any non WebGL <canvas> animation) and exports individual images (or sequences of images) to then be converted into video.

I'm doing this partially as a way to learn Python, but also the idea of being able to rapidly prototype something in Three.js and then export a massive high res silky smooth video is super attractive to me. In the past I've used screen capture software to grab video, though it's always felt a bit clunky and real time drops in FPS would show through in the final video too.

The process I currently have is as follows:

Javascript:

  • Create the WebGL scene and set up a render cycle
  • Set canvas width/height to desired dimensions
  • Render the scene using requestAnimationFrame
  • Pause the scene's render/update cycle
  • Call toDataURL() on the canvas element and retrieve a base64 string
  • POST request to a python script, passing the base64 string (and other things, like target directory to save to and whether a single image or sequence is being captured)

Python:

  • Strip the MIME header content-type and decode the base64 string
  • Write the string to an image file
  • Print / return out a string that signifies successful state if the file was written, otherwise print out the offending error message

import base64, cgi, cgitb, datetime, glob, re, os

cgitb.enable() #cgitb.enable(display=0, logdir='/tmp') print "Content-type: text/html" print def main(): form = cgi.FieldStorage() saveLocation = "../httpdocs/export/" # POST variables dataURL = form['dataURL'].value captureSequence = form['captureSequence'].value folderName = saveLocation + form['folderName'].value saveImage(dataURL, captureSequence, saveLocation, folderName) def saveImage(dataURL, captureSequence, saveLocation, folderName): # strip out MIME content-type (e.g. "data:image/png;base64,") dataURL = dataURL[dataURL.index(','):] decodedString = base64.decodestring(dataURL) if captureSequence == 'true': # based off http://www.akeric.com/blog/?p=632 currentImages = glob.glob(folderName + "/*.jpg") # TODO: perhaps filenames shouldnt start at %08d+1 but rather %08d+0? numList = [0] if not os.path.exists(folderName): os.makedirs(folderName) for img in currentImages: i = os.path.splitext(img)[0] try: num = re.findall('[0-9]+

Javascript:

  • Receive response from Python script and display returned message
  • Resume/update render cycle
  • (Repeat process for as many frames as desired)

This is something that I would always run locally so there would be no risk of conflicting writes or anything of the sort.

I've done a few quick tests and it seems to work for the most part, if a little slow.


  • Am I missing something completely obvious in the way this is being done? How could it be improved? (Especially on the python side of things..)
  • Is it inefficient to do an individual ajax call per image? One advantage is that I can just stop / close the tab at any time and it'll have saved all images up until that point. Would there be any benefit to storing all those base64 strings and sending them all at the very end?
  • As requestAnimationFrame will cap the update cycle to 60fps, is it possible to easily set a lower frame rate? Lets say for some stylistic reason I'd want to update everything at 15fps, would the only option be to use setTimeout(callback, 1000 / targetFPS) with the understanding that it will drift over time?
  • Following on from the above, this animation has a var frame which is being incremented by 1 every update cycle. This var is then used to control varied parts of the animation (e.g, rotating the cube, and being passed to vertex/fragment shaders to manipulate colors and texture UV coordinates).

    If I wanted to simulate something like 15 fps, would I be correct in needing to increment frame by (60 / 15) instead? Is there a more elegant way to be able to easily switch between capped frame rates?

  • Finally, are there any techniques that could be adopted to improve the quality of images being rendered? (Thinking out aloud, saving out at double size then reducing them?)

I really hope that makes sense, any insight or advice would be massively appreciated.

Source files: http://cl.ly/3V46120C2d3B0A1o3o25 (Tested on Mac / Chrome stable, WebGL required)

, i)[0] numList.append(int(num)) except IndexError: pass numList = sorted(numList) newNum = numList[-1] + 1 saveName = folderName + '/%08d.jpg' % newNum else: if not os.path.exists(saveLocation): os.makedirs(saveLocation) saveName = saveLocation + datetime.datetime.now().isoformat().replace(':', '.') + '.jpg' # TODO; rather than returning a simple string, consider returning an object? try: output = open(saveName, 'w') output.write(decodedString) output.close() print 'true' except Exception, e: print e if __name__ == '__main__': main()

Javascript:

  • Receive response from Python script and display returned message
  • Resume/update render cycle
  • (Repeat process for as many frames as desired)

This is something that I would always run locally so there would be no risk of conflicting writes or anything of the sort.

I've done a few quick tests and it seems to work for the most part, if a little slow.


  • Am I missing something completely obvious in the way this is being done? How could it be improved? (Especially on the python side of things..)
  • Is it inefficient to do an individual ajax call per image? One advantage is that I can just stop / close the tab at any time and it'll have saved all images up until that point. Would there be any benefit to storing all those base64 strings and sending them all at the very end?
  • As requestAnimationFrame will cap the update cycle to 60fps, is it possible to easily set a lower frame rate? Lets say for some stylistic reason I'd want to update everything at 15fps, would the only option be to use setTimeout(callback, 1000 / targetFPS) with the understanding that it will drift over time?
  • Following on from the above, this animation has a var frame which is being incremented by 1 every update cycle. This var is then used to control varied parts of the animation (e.g, rotating the cube, and being passed to vertex/fragment shaders to manipulate colors and texture UV coordinates).

    If I wanted to simulate something like 15 fps, would I be correct in needing to increment frame by (60 / 15) instead? Is there a more elegant way to be able to easily switch between capped frame rates?

  • Finally, are there any techniques that could be adopted to improve the quality of images being rendered? (Thinking out aloud, saving out at double size then reducing them?)

I really hope that makes sense, any insight or advice would be massively appreciated.

Source files: http://cl.ly/3V46120C2d3B0A1o3o25 (Tested on Mac / Chrome stable, WebGL required)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

浅笑轻吟梦一曲 2025-01-03 15:34:16

在 EaselJS 中,您可以设置 FPS

EaselJS Ticker

In EaselJS you can set FPS

EaselJS Ticker

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文