野生Js技术之字符画视频

来源:互联网 发布:超人电力工程造价软件 编辑:程序博客网 时间:2024/04/28 16:26

目录(?)[+]

废话不多说,先看效果




http://www.bilibili.com/video/av3502072/ 这是视频链接,文章后面再给demo 链接

原理及实现方式

1.图片转字符画

还是那句话,先看效果图:



Demo 地址:
http://followyourheart.sinaapp.com/Demos/char_pic.html

首先,我们需要分析图片,获取相应的灰度信息。比如说,我们如果想要生成每行100个字符的字符画,我们就需要知道每一个字符所对应图片中的哪一块区域,我们将该区域的平均灰度获取到,然后输出一个字符,
我们用不同的字符来表示灰度,比如@表示最黑,空白表示白色,依次为:
[javascript] view plaincopy
  1. ['@''w''#''$''k''d''t''j''i''.'' '];  

所以,具体步骤为:

1.拿到基本信息(图片的宽高,图片的像素数据,每行要生成的字符数),通过将img画到canvas,再从canvas中获取context.drawImage(img, 0, 0, canvas.width, canvas.height);
2.计算出每个字符对应图片中的像素宽度(比如图片宽1000px,每行字符数是100个。那么每个字符对应图片中10px,这里不用考虑文字宽度,文字宽度不会影响逻辑)。由于字符的列数是一开始由每行字符数确定的,所以只要计算行数,之前计算过每个字符对应图片的像素宽度,那么高度的对应关系也是如此,将图片的高度除以该倍数就是文字的行数。
3.根据文字的行列数,开始遍历。根据图片的像素数据,在其中找出合适的灰度,然后输出对应的字符。比如,我要输出第2行第2列的文字,我就去图片中找(x:1*10,y:1*10)到(x:2*10,y:2*10)的一个矩形区域,遍历每个点的RGB值。通过gray =R * 0.3 + G * 0.59 + B * 0.11来确定某一像素点的灰度,再求这一区域的平均灰度,根据这个灰度来确定字符。
4.在字符的一行结束时输出“\r\n”以换行,获取到整个字符串后,将其放入<pre>标签忠呈现。

注意点:

所谓的imageData,其实是通过canvas的context.getImageData(x,y,width,height)方法获取到的一个对象,其中imageData.data属性可以拿到所有rgba值,比如imageData.data[0]代表第一个像素点的R值,所以每四个数表示一个像素。所以我们要获取x,y点的RGBA值,通过index = (y * imageData.width + x) * 4 可以计算得到R值data[index],index依次+1,+2就为G,B的值,A值此处无实际作用。
还有一点要注意的是,生成的字符串要塞到<pre>标签中,不可以是div,一些特殊符号在pre中才能正常显示。
这里也是最核心的代码:如下:
[javascript] view plaincopy
  1. /** 
  2.  * Created by Roger on 16/1/2. 
  3.  */  
  4. var map=getCharsMap();  
  5. /* 
  6.  * this function can convert the image in canvas to a char-picture(string) 
  7.  * cotext:the canvas context;width:the image width;height:the image height; rowChars:how many chars in one row. 
  8.  */  
  9. function toChars(context, width, height, rowChars) {  
  10.     var pixels = [],  
  11.         output = "",  
  12.         imageData = context.getImageData(0, 0, width, height),  
  13.         rowChars = width < rowChars ? width : rowChars,  
  14.         char_h = width / rowChars,  
  15.         char_w = char_h,  
  16.         rows = height / char_h,  
  17.         cols = rowChars,  
  18.     //to get a block of pixiels average gray-value.  
  19.         getBlockGray = function (x, y, w, h) {  
  20.             var sumGray = 0, pixels;  
  21.             for (var row = 0; row < w; row++) {  
  22.                 for (var col = 0; col < h; col++) {  
  23.                     var cx = x + col, //current position x  
  24.                         cy = y + row, //current positon y  
  25.                         index = (cy * imageData.width + cx) * 4, //current index in rgba data array  
  26.                         data = imageData.data,  
  27.                         R = data[index],  
  28.                         G = data[index + 1],  
  29.                         B = data[index + 2],  
  30.                         gray = ~~(R * 0.3 + G * 0.59 + B * 0.11);  
  31.                     sumGray += gray;  
  32.                 }  
  33.             }  
  34.             pixels = w * h;  
  35.             return ~~(sumGray / pixels);  
  36.         };  
  37.     for (var r = 0; r < rows; r++) {  
  38.         for (var c = 0; c < cols; c++) {  
  39.             var pos_x = ~~(c * char_h),  
  40.                 pos_y = ~~(r * char_h),  
  41.                 avg = getBlockGray(pos_x, pos_y, ~~char_w, ~~char_h),  
  42.                 ch = map[avg];  
  43.             output += ch;  
  44.         }  
  45.         output += '\r\n';  
  46.     }  
  47.     ;  
  48.     return output;  
  49. }  
  50. function getCharsMap() {  
  51.     var chars = ['@''w''#''$''k''d''t''j''i''.'' '];  
  52.     var step = 25,  
  53.         map = {};  
  54.     for (var i = 0; i < 256; i++) {  
  55.         var index = ~~(i / 25)  
  56.         map[i] = chars[index];  
  57.     }  
  58.     ;  
  59.     return map;  
  60. }  
以下是其余逻辑代码:
[html] view plaincopy
  1. <!DOCTYPE html>  
  2. <html>  
  3. <head lang="en">  
  4.     <meta charset="UTF-8">  
  5.     <title>Char Picture</title>  
  6.     <style>  
  7.         #show{  
  8.             font-family: Courier New;  
  9.             font-size: 10px;  
  10.             line-height: 8px;  
  11.         }  
  12.     </style>  
  13.     <script src="source/js/pic_to_chars.js"></script>  
  14. </head>  
  15. <body>  
  16. <input type="file" id="file"><button type="button" onclick="showImage()">展示</button><br>  
  17. <img src="" style="width: 100px"/>  
  18. <pre id="show"></pre>  
  19. <script>  
  20.     var map=getCharsMap(),show=document.getElementById("show"),  
  21.             img=document.getElementsByTagName("img")[0],  
  22.             canvas = document.createElement("canvas");  
  23.     function showImage(){  
  24.         var file = document.getElementById('file').files[0],  
  25.                 ctx = canvas.getContext('2d'),  
  26.                 url = URL.createObjectURL(file);  
  27.         if(!file){  
  28.             alert("请先选择文件");  
  29.         }  
  30.         img.src = url;  
  31.         img.onload=function(){  
  32.             canvas.width=img.naturalWidth;  
  33.             canvas.height=img.naturalHeight;  
  34.             ctx.drawImage(img, 0, 0, canvas.width, canvas.height);  
  35.             show.innerText=toChars(ctx,canvas.width,canvas.height,100);  
  36.         }  
  37.     }  
  38. </script>  
  39. </body>  
  40. </html>  



2.视频转图片

前面我们提到的图片转字符串,需要先将图片画到canvas上,再通过context的api来获取图片数据。我们知道canvas是可以画各种元素的,比如这里的视频,或者svg等等。所以,我们只需要建立video标签,然后加载视频,将视频draw到canvas上(此时为视频当前的一帧),然后通过之前的getImageData方法就可以拿到图片信息了,调用之前绘制图片的方法来绘制视频当前帧。
所以,只需要设立个timeInterval,让这个方法一秒走10次,这样字符画就会一秒更新10次,以带到同步视频播放的效果。
下面是视频的demo,由于在服务器上加载视频费用吃不消,所以视频给大家个链接,自己加载到demo上面吧。
demo:http://followyourheart.sinaapp.com/Demos/char_video.html
视频:链接: http://pan.baidu.com/s/1qWW5mhA 密码: ixsj 
可以下载视频,然后打开demo,点击选择该视频文件,然后点击播放。

视频部分逻辑代码如下

[html] view plaincopy
  1. <!DOCTYPE html>  
  2. <html>  
  3. <head lang="en">  
  4.     <meta charset="UTF-8">  
  5.     <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no"/>  
  6.     <title>Char Video</title>  
  7.     <style>  
  8.         html, body {  
  9.             width: 100%;  
  10.         }  
  11.   
  12.         video {  
  13.             margin: auto;  
  14.             position: relative;  
  15.             top: 0;  
  16.             left: 0;  
  17.             width: 20%;  
  18.             height: 20%;  
  19.         }  
  20.   
  21.         #stage {  
  22.             margin: auto;  
  23.             position: absolute;  
  24.             top: 0;  
  25.             left: 20%;  
  26.             right: 0;  
  27.             width: 80%;  
  28.             font-family: Courier New;  
  29.             font-size: 16px;  
  30.             line-height: 10px;  
  31.         }  
  32.   
  33.         #stage img {  
  34.             width: 100%;  
  35.             height: 100%;  
  36.         }  
  37.     </style>  
  38.     <script src="source/js/pic_to_chars.js"></script>  
  39. </head>  
  40. <body>  
  41. <input type="file" id="file">  
  42. <button id="play" type="button" onclick="play()">播放</button>  
  43. <br>  
  44. <video controls="controls">  
  45.   
  46. </video>  
  47. <!--<div id="stage"></div>-->  
  48. <pre id="stage"></pre>  
  49. <script type="text/javascript">  
  50.     var interval, video = document.getElementsByTagName("video")[0],  
  51.             stage = document.getElementById("stage"),  
  52.             canvas = document.createElement("canvas"),  
  53.             captureImage = function () {  
  54.                 var ctx;  
  55.                 canvas.width = video.videoWidth;  
  56.                 canvas.height = video.videoHeight;  
  57.                 if (canvas.width) {  
  58.                     ctx = canvas.getContext('2d');  
  59.                     ctx.clearRect(0, 0, canvas.width, canvas.height);  
  60.                     ctx.drawImage(video, 0, 0, canvas.width, canvas.height);  
  61.                     stage.innerText = toChars(ctx, canvas.width, canvas.height, 100);  
  62.                 }  
  63.             },  
  64.             beginCapture = function () {  
  65.                 interval = setInterval(function () {  
  66.                     captureImage(1)  
  67.                 }, 100);  
  68.             },  
  69.             endCapture = function () {  
  70.                 if (interval) {  
  71.                     clearInterval(interval);  
  72.                 }  
  73.             },  
  74.             play = function () {  
  75.                 var file = document.getElementById('file').files[0];  
  76.                 var url = URL.createObjectURL(file);  
  77.                 if (!file) {  
  78.                     alert("请先选择文件");  
  79.                 }  
  80.                 console.log(url);  
  81.                 video.src = url;  
  82.                 video.play();  
  83.             };  
  84.     video.addEventListener("play", beginCapture);  
  85.     video.addEventListener("pause", endCapture);  
  86.     video.addEventListener("ended", endCapture);  
  87.     video.addEventListener("playing", function () {  
  88.         endCapture();  
  89.         beginCapture();  
  90.     });  
  91. </script>  
  92.   
  93. </body>  
  94. </html>  

0 0
原创粉丝点击