JavascriptHTML5视频转字符动画

12

HTML5视频转字符动画

HTML5视频转字符画 字符动画 HTML5画布 阅读:3515 时间:2018年08月29日

什么是视频转字符动画?大至就是点此在线体验 (纯字符,适合PC)         点此在线体验 (画布,适合PC、移动端)补充: 字符画进阶实现(后来写的一个更有意思的实现,可以自己手写字符生成图画,比如写个女...


什么是视频转字符动画?大至就是


点此在线体验 (纯字符,适合PC)         点此在线体验 (画布,适合PC、移动端)


补充: 字符画进阶实现(后来写的一个更有意思的实现,可以自己手写字符生成图画,比如写个女友的名字。。)


image.pngimage.png


一. 实现原理

  1. 将视频拆分成独立的帧(等同于每换一个画面截一张图)

  2. 将图转换成黑白图(如何将图片转成黑白自行百度)后,拿到每个像素点的颜色,点的rgb是相同的,是一个0~255的整数,0表示完全黑,255表示完全白

  3. 准备一组字符,比如@#&!:,. 这些字符同字号时的表面积应该是递增的且越多越好,对应2中的rgb数值,比如 @对应0,表示黑,  .对应255表示白

  4. 将2中的黑白图片每个像素点换成3中对应的字符

  5. 重复1-4步到视频结束

二. 代码实现

    最下面有完整源码,实现部分能理清思路就好。

    准备cnavas容器,用来播放字符动画。准备input文件上传入口

                <canvas id="textCanvas"></canvas>
                <input type="file" id="file">


    创建一个视频转动画的Dv类,构造中初始化画布

function Dv(){
    this.textCanvas = $('textCanvas');
    this.textCanvas.width = window.innerWidth;
    this.textCanvas.height = window.innerHeight;
    this.textCtx = this.textCanvas.getContext('2d');
}


    创建一个离屏video容器,用来播放原视频,是字符画的数据来源

Dv.prototype.initVideo = function(src) {
    if(!this.video){
        this.video = document.createElement('video');
    }
    if(src){
        this.video.src = src;
    }
};


    获取并使用FileReader对象做为Blob对象载入

Dv.prototype.initFile = function() {
    var file = $('file').files[0];
    if(!file){
        alert("请选择一个MP4视频文件");
        return false;
    }
    var reader = new FileReader();
    var buffer = [];
    var that = this;
    reader.onload = function(){
        var blob = new Blob([reader.result], { type: 'video/mp4'});
        that.playFile(reader.result,blob);
    }
    reader.readAsArrayBuffer(file);
};


    将载入的Blob对象做为ObjectURL赋值给video的src属性,video可以播放视频文件了,下面开始抓取视频帧来生成字符画

Dv.prototype.playFile = function(arrayBuffer,blob) {
    var mediaSource = new MediaSource();
    src = URL.createObjectURL(blob);
    this.initVideo(src);
    this.interval();
    this.video.play();
};


    使用requestAnimationFrame动画API开始定时抓取视频单帧图像转换成黑白。ctx是离屏画布的上下文用于临时存放图像,ctx将图像缩小一定比列来减少要处理的像素点。

Dv.prototype.interval = function() {
    var that = this;
    requestAnimationFrame(function(){
        if(!that.video.paused){
            that.ctx.drawImage(that.video,0,0,that.width,that.height);
            var data = that.loadData();
            that.reDraw(data);
            that.drawText();
        }
        that.interval();
    });
};

    

    将图像像素点数据,按黑白程度映射成相应的字符,并输出在画布上

Dv.prototype.drawText = function() {
    this.textCtx.clearRect(0,0,window.innerWidth,window.innerHeight);
    var data = this.data.data;
    var points = '.,`":!^|*ITDXUHB%&#@NM'.split('');
    for(var i=0,len=data.length;i<len;i+=4){
        this.textCtx.fillStyle = '#333';
        var xl = (i/4|0)%this.width;
        var yl = Math.ceil(i/4/this.width);
        var x = xl * this.space;
        var y = yl * this.space;
        var newData = data[i] | 0;
        var plen = Math.ceil(255/points.length);
        var point = points[newData/plen  | 0]
        this.textCtx.font="12px courier";
        this.textCtx.fillText(point,x,y);
    }
};



三. 完整示例,(控制台开启手机模式效果最好)

<!DOCTYPE html>
<html>
<head>
	<title>chars video</title>
	<style type="text/css">
		html,body{ height: 100%; }
		html,body,.ctrl{margin: 0;padding: 0;}
		#textCanvas{ font-family: 'courier';}
		#videoScreen{ height: 100vh;width: 100vw;}
		.ctrl{ position: fixed;right: 0px;bottom: 0px;left: 0;z-index: 3;  padding: 10px; border-radius: 4px;background-color: #fff;}
		input[type=button]{background-color: #1aa988;color: #fff;border-width: 0;padding: 4px 8px;border-radius: 4px;cursor: pointer;}
		select{padding : 4px 8px;border-radius: 4px;}
		#file{position : absolute;left:-99999px;}
		.file{background-color: #1aa988;color: #fff;border-width: 0;padding: 12px 8px;border-radius: 96px;cursor: pointer;display: block;text-align: center;}
		#info{position: absolute;text-align: center;padding: 20px;left: 0;right: 0; top: 20%;color: #999;line-height: 2;}
	</style>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0,user-scalable=no,minimal-ui">
</head>
<body>
	<div class="ctrl" id="ctrl">
		<label for="file" class="file"><input type="file" id="file">浏览</label>
	</div>
	<div id="videoScreen">
		<div id="info">点击浏览,选择一个mp4视频文件即可开始<br>点击空白处可隐藏底部按钮</div>
		<canvas id="textCanvas"></canvas>
	</div>
	<script type="text/javascript">
		function $(id){ return document.getElementById(id); }

		function Dv(){
			this.space = 10;
			this.width = Math.ceil(window.innerWidth/this.space);
			this.height = Math.ceil(window.innerHeight/this.space);
			this.data = {};
			this.cav = {};
			this.ctx = {};
			this.playing = false;
			this.init();
			this.scaleX = window.innerWidth/this.width;
			this.textCanvas = $('textCanvas');
			this.textCanvas.width = window.innerWidth;
			this.textCanvas.height = window.innerHeight;
			this.textCtx = this.textCanvas.getContext('2d');
		}

		Dv.prototype.init = function() {
			this.initVideo();
			this.initCanvas();
			this.cav.width = this.width;
			this.cav.height = this.height;
			this.initEvent();
		};

		Dv.prototype.initVideo = function(src) {
			if(!this.video){
				this.video = document.createElement('video');
				//document.body.appendChild(this.video);
			}
			if(src){
				this.video.src = src;
			}
		};

		Dv.prototype.initCanvas = function(video) {
			this.cav = document.createElement('canvas');
			this.ctx = this.cav.getContext('2d');
		};

		Dv.prototype.loadData = function() {
			return this.ctx.getImageData(0,0,this.width,this.height);
		};

		Dv.prototype.reDraw = function(data) {
			for(var i=0,len=data.data.length;i<len;i+=4){
				var r = data.data[i],
					g = data.data[i+1],
					b = data.data[i+2];
				data.data[i] = data.data[i+1] = data.data[i+2] = 255-(r+g+b)/3 | 0;
			}
			this.data = data
			this.ctx.putImageData(data,0,0,0,0,this.width,this.height);

		};

		Dv.prototype.drawText = function() {
			this.textCtx.clearRect(0,0,window.innerWidth,window.innerHeight);
			var data = this.data.data;
			var points = '.,`":!^|*ITDXUHB%&#@NM'.split('');
			for(var i=0,len=data.length;i<len;i+=4){
				this.textCtx.fillStyle = '#333';
				var xl = (i/4|0)%this.width;
				var yl = Math.ceil(i/4/this.width);
				var x = xl * this.space;
				var y = yl * this.space;
				var newData = data[i] | 0;
				var plen = Math.ceil(255/points.length);
				var point = points[newData/plen  | 0]
				this.textCtx.font="12px courier";
				this.textCtx.fillText(point,x,y);

			}

		};

		Dv.prototype.interval = function() {
			var that = this;
			requestAnimationFrame(function(){
				if(!that.video.paused){
					that.ctx.drawImage(that.video,0,0,that.width,that.height);
					var data = that.loadData();
					that.reDraw(data);
					that.drawText();
				}
				that.interval();				
			});
		};

		//以下方法用于本地视频
		Dv.prototype.initEvent = function() {
			var that = this;
			$('file').onchange = function(){
				var filename = this.value;
				var index = filename.lastIndexOf(".");
				var ext = filename.substr(index+1);
				if(ext == "mp4"){
					that.initFile();
					$('info').style.display = 'none';
					$('ctrl').style.display = 'none';
				}else{
					alert("仅支持MP4格式");
				}
			}
			$('videoScreen').onclick = function(){
				if($('ctrl').style.display == 'none'){
					$('ctrl').style.display = 'block';
				}else{
					$('ctrl').style.display = 'none';
				}
			}
		};
		Dv.prototype.initFile = function() {
			var file = $('file').files[0];
			if(!file){
				alert("请选择一个MP4视频文件");
				return false;
			}
			var reader = new FileReader();
			var buffer = [];
			var that = this;
			reader.onload = function(){
				var blob = new Blob([reader.result], { type: 'video/mp4'});
				that.playFile(reader.result,blob);
			}
			reader.readAsArrayBuffer(file);
		};
		Dv.prototype.playFile = function(arrayBuffer,blob) {
			var mediaSource = new MediaSource();
        	src = URL.createObjectURL(blob);
        	this.initVideo(src);
        	this.interval();
        	this.video.play();
		};

		var d = new Dv();
	</script>
</body>
</html>


发表评论说说你的看法吧

  • 访客
    访客 2018-12-13 11:41:53
    怎样把转换好的视频保存出来呢

精品模板蓝瞳原创精品网站模板

^