js視頻轉字符畫 —— 寫一個屬于自己的字符轉換器
先看一下效果,原視頻是這樣的。我們要實現的效果是這樣子的。之所以找這個視頻是因為...這個視頻和背景的對比度比較高做出來比較有辨識度,沒有其他的意思 ( *?ω?)?╰ひ╯
起因
某天一個基友在群里問我,在抖音看到一種視頻,問我是不是能實現。我說可以的~ 于是當天晚上花了一個多小時折騰了一個粗糙版本...
先把視頻丟到部門技術群問有沒有關鍵字,給了一個keyword 圖片轉字符串 于是照著這個思路去gayhub找資源拼樂高!
整體思路
第一步我們通過
input[type="file"]獲取文件拿到文件之后,用
URL.createObjectURL來獲取視頻的路徑通過
ctx.drawImage我們可以把某個 video 當前的圖像渲染到 canvas里面ctx.getImageData可以獲取當前canvas 里面圖片的色值,利用公式計算出灰度根據灰度的深淺去匹配要顯示的字符,深色匹配比較密集的字符,最淺的直接用空格代替,然后用
ctx.fillText重繪進去設置
video.currentTime來獲得視頻的某一時刻圖像,重復上述重繪過程
既然大概的思路已經理清,接下來就是具體的編碼,把想法寫出來的過程
具體編碼實現
獲取視頻文件
首先我們先確定下html需要哪些元素
一個 input[type="file"] 用來給用戶上傳的
一個 video 用來承載上傳的video
一個 canvas 用來展示最終的結果
大概是長這樣:
<input type="file" id="inputFile" accept=".mp4" /> <canvas id="canvasShow"></canvas> <video id="video"></video> 復制代碼
接下來js文件,我們要先對 input 綁定個監聽事件,拿到文件url之后設置給video
這里要注意兩點,一個是 url用完不用的話,用 URL.revokeObjectURL 釋放資源; 一個是我們這里用了 await 在domVide.onCanplay之前不做任何操作,防止視頻沒有加載完就操作,有黑屏風險。
如果對 es6、es7、es8不熟悉的小伙伴要去補一下了~ 現在基本不會這些基本語法都看不懂demo= = 附上阮一峰老師的ES6教程,又想起面試被問ES7有什么新特性 簡直是*了狗
domInput.addEventListener('change', async({target: {files }})=> { const file = files[0]; const url = URL.createObjectURL(file); domVideo.src = urlrl; await new Promise(res=> domVideo.addEventListener('canplay', res)); // next ====> handleVideoInit() }); 復制代碼
將視頻渲染到canvas
拿到視頻之后,我們要把當前這一個時刻的圖像渲染到canvas里面 先用ctx.drawImage(video, 0, 0, width, height) 把video dom當前屏渲染進canvas
再用ctx.getImageData(0, 0, width, height) 獲取圖片的色值來做處理
可以通過調整 img2Text 來選擇渲染出來的圖片是想要怎樣的(由哪些字符組成等等)
比如把 textList改為 ['Aa', 'Bv', 'Cc', 'Dd', '#', '&', '@', '$', '*', '?', ';', '^', '·', '·', '·', '·'],辨識度會高一點
/* domVide => video元素 size => 存放video等元素的長寬 canvasVideo => 存放video當前的圖像的canvas canvasShow => 存放最后展示效果的canvas */ const size = {w: 0, h: 0}; const canvasVideo = document.createElement('canvas'); function handleVideoInit() { domVideo.currentTime = 0; size.w = domVideo.width = canvasVideo.width = canvasShow.width = domVideo.videoWidth * .5; size.h = domVideo.height = canvasVideo.height = canvasShow.height = domVideo.videoHeight * .5; video2Img(); } function video2Img() { const { w, h } = size; ctxVideo.drawImage(domVideo, 0, 0, w, h); const { data } = ctxVideo.getImageData(0, 0, w, h); ctxShow.clearRect(0, 0, w, h); for (let _h= 0; _h< h; _h+= 8) { for (let _w= 0; _w< w; _w+= 8) { const index = (_w + w * _h) * 4; const r = data[index + 0]; const g = data[index + 1]; const b = data[index + 2]; const gray = .299 * r + .587 * g + .114 * b; ctxShow.fillText(img2Text(gray), _w, _h + 8); } } } function img2Text(g) { const i = g % 16 === 0 ? parseInt(g / 16) - 1 : parseInt(g/ 16); return ['#', '&', '@', '%', '$', 'w', '*', '+', 'o', '?', '!', ';', '^', ',', '.', ' '][i]; } 復制代碼
到這一步,其實已經實現了把一張圖片變為字符填充圖了,剩下的工作無非就是把視頻變成一張張的圖片,然后重復執行這些邏輯
持續調用渲染字符視頻
我們改一下 video2Img 函數,將其實現為能持續調用的形式, 再添加一個函數 clear 用來清理垃圾
這里用到的是 window.requestAnimationFrame 去持續調用
function video2Img({ timePoint= 0, curT= Date.now(), prevT= Date.now(), prevInterval, }) { const { w, h } = size; ctxVideo.drawImage(domVideo, 0, 0, w, h); drawOnce(); let _interval = Math.max((curT - prevT), 16) / 1000; if (curT - prevT !== 0) _interval -= prevInterval; await new Promise(res=> setTimeout(res, _interval*1000)); const nextTimePoint = _interval + timePoint; if (nextTimePoint > domVideo.duration) return clear(); tId = window.requestAnimationFrame(()=> video2Img({ timePoint: nextTimePoint, prevT: curT, curT: Date.now(), prevInterval: _interval, })); } function drawOnce() { const { data } = ctxVideo.getImageData(0, 0, w, h); ctxShow.clearRect(0, 0, w, h); for (let _h= 0; _h< h; _h+= 8) { for (let _w= 0; _w< w; _w+= 8) { const index = (_w + w * _h) * 4; const r = data[index + 0]; const g = data[index + 1]; const b = data[index + 2]; const gray = .299 * r + .587 * g + .114 * b; ctxShow.fillText(img2Text(gray), _w, _h + 8); } } } function cleart() { const {w, h} = size; lastUrl && URL.revokeObjectURL(lastUrl); tId && window.cancelAnimationFrame(tId); ctxShow.clearRect(0, 0, w, h); ctxVideo.clearRect(0, 0, w, h); } 復制代碼
源碼與demo
至此,功能基本都實現了,下面提供在線的呆毛和github倉庫地址~
pc端在線呆毛
github 源碼
video轉圖片忘了是在github看哪個項目的,ctx.drawImage(video, 0, 0, width, height)這個是看完才知道的。
圖片轉字符基本是看這個大哥的github
在找方案的時候看到的一個像素圖實現,挺有趣的,以前實現馬賽克是拿周圍像素值取平均去做,這個哥們是直接放大截圖 更簡單粗暴傳送門
作者:Jsonz
來源:掘金
工程師必備
- 項目客服
- 培訓客服
- 平臺客服
TOP




















