将HTML画布导出为图像序列
我创建了一个非常简单的页面,它可以处理一个Three.js场景(其实也可以是任何非WebGL的<canvas>
动画),并将单独的图像(或者图像序列)导出,之后再转换成视频。
我这样做部分是为了学习Python,但更重要的是,能够快速在Three.js中制作原型,然后导出高分辨率、流畅的视频,这个想法对我来说非常吸引人。以前我用过屏幕录制软件来抓取视频,但总感觉有点笨重,而且实时录制时帧率下降会在最终视频中显现出来。
我现在的流程如下:
JavaScript:
- 创建WebGL场景并设置渲染循环
- 设置画布的宽度和高度为所需的尺寸
- 使用
requestAnimationFrame
来渲染场景 - 暂停场景的渲染/更新循环
- 调用
toDataURL()
方法获取画布的base64字符串 - 向Python脚本发送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变量
dataURL = form['dataURL'].value
captureSequence = form['captureSequence'].value
folderName = saveLocation + form['folderName'].value
saveImage(dataURL, captureSequence, saveLocation, folderName)
def saveImage(dataURL, captureSequence, saveLocation, folderName):
# 去掉MIME内容类型(例如 "data:image/png;base64,")
dataURL = dataURL[dataURL.index(','):]
decodedString = base64.decodestring(dataURL)
if captureSequence == 'true':
# 基于 http://www.akeric.com/blog/?p=632
currentImages = glob.glob(folderName + "/*.jpg")
# TODO: 文件名是否应该从 %08d+1 开始,而不是 %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: 而不是返回一个简单的字符串,考虑返回一个对象?
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)
,但这样会随着时间的推移而漂移吗? 接着上面的问题,这个动画有一个变量
frame
,每次更新循环时递增1
。这个变量用于控制动画的不同部分(例如,旋转立方体,并传递给顶点/片段着色器以操控颜色和纹理坐标)。如果我想模拟15fps,我是否应该将
frame
递增(60 / 15)
呢?有没有更优雅的方法来轻松切换帧率?- 最后,有没有什么技术可以用来提高渲染图像的质量?(我在想,是否可以先保存为双倍大小再缩小?)
我希望这些内容能让你明白,任何见解或建议都将非常感谢。
源文件: http://cl.ly/3V46120C2d3B0A1o3o25(在Mac / Chrome稳定版上测试,需要WebGL)