江西南昌建设厅网站,wordpress突然很慢,地区汽车修理网站建设,中医协会网站建设方案今天玩一些新的东西#xff0c;大家都没有看过这样的视频#xff1a;或者 这样的图片#xff1a;网上有很多生成这种图片/视频的工具#xff0c;但是每个程序员都有一颗造轮子的心#xff0c;我们当然要玩出自己的花样啦。老规矩#xff0c;还是先讲原理#xff0c;建议…今天玩一些新的东西大家都没有看过这样的视频或者 这样的图片网上有很多生成这种图片/视频的工具但是每个程序员都有一颗造轮子的心我们当然要玩出自己的花样啦。老规矩还是先讲原理建议先用自己的方式实现一遍。原理很简单首先准备一组排好序的不同 “着色密度 ” 的ascii字符(事实上你可以用任何字符)比如 #KDGLftji;,:. 接着将源图转为灰度图然后遍历图中的像素根据r/g/b通道的值来匹配字符串中相应 “着色密度 ” 的字符值越小则颜色越深字符的“密度”也应越大。如果需要保留颜色只需将灰度图和原图的像素位置一一对应即可。在开始实现功能之前我们需要先了解一下颜色矩阵(ColorMatrix)。在计算机中每个像素的颜色可以用一个向量(有的文章也叫矢量或分量)矩阵表示[R, G, B, A]。颜色变换矩阵通常是用一个5x5的矩阵来表示和空间中一个n维向量的平移变换需要用一个n1维的矩阵来表示一样颜色矩阵也需要引入一个齐次坐标来进行“平移操作”。以下是一些常见的颜色变换矩阵亮度矩阵RGBAWR1000bG0100bB0010bA00010W00001反色矩阵RGBAWR-1002550G0-102550B00-12550A00010W00001灰度矩阵RGBAWR0.30860.60940.082000G0.30860.60940.082000B0.30860.60940.082000A00010W00001ps将像素去色的原理是使RGB同时为了保持亮度不变须使RGB尽量等于1 理论上来说要平分R、G、B通道值应该是(RBG)/3即系数应该约为0.3333才对之所以比例不同按照网上的解释这个比例主要是根据人眼中三种不同的感光细胞的感光强度比例分配的还有一组比较常用的比例是0.21250.71540.0721至于怎么来的还希望哪位大佬指点迷津。下面是页面的html结构ascii art* {margin: 0;padding: 0;}canvas, img, #container {display: block;margin: auto;}#container {line-height: 12px;font-size: 12px;font-family: SimHei, monospace;letter-spacing: 6px;}(function () {// 这里是js代码})()解释一下几个关键点首先我们输出的文字必须是等宽字体我这里使用的是黑体font-family: SimHei, monospace; 别忘了加上fallbackmonospace。等宽字体是指每个字宽高都固定的字体这里的固定宽高是指同一种文字比如中文的黑体宽度是英文的两倍其他字体我没有试过大家可以自己去实验。这也是我设置了 letter-spacing: 6px; 的原因当黑体设置了font-sizeline-height时中文是宽高相等英文宽是高的一半。接下来是js代码var container document.getElementById(container)var offScreenCvs document.createElement(canvas) // 创建离屏canvasvar offScreenCtx offScreenCvs.getContext(2d, { alpha: false }) // 关闭透明度var offScreenCvsWidth, offScreenCvsHeightvar samplerStep 4 // 采样间隔var img new Image()var onImgLoaded function () {offScreenCvsWidth img.widthoffScreenCvsHeight img.heightoffScreenCvs.width offScreenCvsWidthoffScreenCvs.height offScreenCvsHeightoffScreenCtx.drawImage(img, 0, 0, offScreenCvsWidth, offScreenCvsHeight)imageData offScreenCtx.getImageData(0, 0, offScreenCvsWidth, offScreenCvsHeight)// 采样点数 图片宽度 / 采样间隔容器边长 采样点数 × 字体大小container.style.width (offScreenCvsWidth / samplerStep * 12) pxcontainer.style.height (offScreenCvsHeight / samplerStep * 12) pxrender()}img.src ./trump.pngimg.complete ? onImgLoaded() : (img.onload onImgLoaded) // 确保onImgLoaded被执行var imageDatavar x, y, posvar asciiCharArray #KDGLftji;,:..split() // 准备不同密度的字符数组(降序)var durationPerChar Math.ceil(255 / asciiCharArray.length) // 每个字符代表的密度阈值function render () {var imageDataContent imageData.datavar strArray []var part1, part2var lettervar valuefor (y 0; y offScreenCvsHeight; y samplerStep) {strArray.push() // 使用P标签换行for (x 0; x offScreenCvsWidth; x samplerStep) {pos y * offScreenCvsWidth x// 获取RBG加权平均后的灰度值value imageDataContent[pos * 4] * 0.3086 imageDataContent[pos * 4 1] * 0.6094 imageDataContent[pos * 4 2] * 0.0820imageDataContent[pos * 4] imageDataContent[pos * 4 1] imageDataContent[pos * 4 2] value// 判断灰度值落在那个密度范围中拿到对应的字符part1 Math.floor(value / durationPerChar)part2 value % durationPerCharletter part2 ? asciiCharArray[part1] : (part1 ? asciiCharArray[part1 - 1] : æ)strArray.push(letter)}strArray.push()}container.innerHTML strArray.join()}先解释一下这行img.complete ? onImgLoaded() : (img.onload onImgLoaded)通常来说img.onload 必须要放在 img.src 之前来保证onload回调一定会执行否则的话如果图片在执行这段代码之前已经被浏览器缓存了则有可能不会触发onload回调。但是有时候由于业务的需要有些操作必须要在图片载入完成后执行可是不一定立即执行碰到这种情况就可以用到Image对象的complete属性该属性会返回当前图片是否加载完成的bollean值。于是通过上面这行代码就可以确保onImgLoaded函数在图片载入完成后一定会被触发。(本案例该写法不必须但是建议养成这个习惯)上面实际上已经完成了核心的功能接下来对我们的代码做一些优化——如果我们需要提供改变字体大小的功能怎么办可以先直接把字体大小相关的字面值抽出为一个变量如fontSize ......var fontSize 18 // 字体大小......var onImgLoaded function () {......container.style.width (offScreenCvsWidth / samplerStep * fontSize) pxcontainer.style.height (offScreenCvsHeight / samplerStep * fontSize) pxcontainer.style.fontSize fontSize pxcontainer.style.lineHeight fontSize pxcontainer.style.letterSpacing (fontSize / 2) px // SimHei体英文宽是高的一半render()}但是PC浏览器不允许字体小于12px怎么办呢我们可以用css的scale来缩放容器就行了修改代码如下...var onImgLoaded function () {......imageData offScreenCtx.getImageData(0, 0, offScreenCvsWidth, offScreenCvsHeight)if (fontSize 12) {// 小于12px则将字体改为12px并通过 transform scale 进行缩放container.style.transform scale( (fontSize / 12) )container.style.transformOrigin 50% 0fontSize 12}container.style.width (offScreenCvsWidth * fontSize / samplerStep) px......}...好了现在我们生成的是灰色的图但是如何生成彩色的图呢估计大家第一反应就是给每个字外面包一层标签(比如span、font)但是笔者试了之后发现一旦图片尺寸稍微大一些性能下降非常夸张一度把我的浏览器给弄崩溃了(╥╯^╰╥)小伙伴们可以自行尝试。于是我打算用canvas来做渲染而不是使用开销极大的dom上面的代码大部分可以重用我修改了一下后的html结构ascii art* {margin: 0;padding: 0;}canvas, img {display: block;margin: auto;}(function () {// canvas 实现})()这是js代码var offScreenCvs document.createElement(canvas)var offScreenCtx offScreenCvs.getContext(2d, { alpha: false })var asciiCvs document.getElementById(ascii-canvas)var asciiCtx asciiCvs.getContext(2d, { alpha: false })var offScreenCvsWidth, offScreenCvsHeight, asciiCvsWidth, asciiCvsHeightvar fontSize 8var samplerStep 4var img new Image()var onImgLoaded function () {offScreenCvsWidth img.widthoffScreenCvsHeight img.heightoffScreenCvs.width offScreenCvsWidthoffScreenCvs.height offScreenCvsHeightoffScreenCtx.drawImage(img, 0, 0, offScreenCvsWidth, offScreenCvsHeight)imageData offScreenCtx.getImageData(0, 0, offScreenCvsWidth, offScreenCvsHeight)asciiCvsWidth offScreenCvsWidth / samplerStep * fontSizeasciiCvsHeight (offScreenCvsHeight / samplerStep 1) * fontSizeasciiCvs.width asciiCvsWidthasciiCvs.height asciiCvsHeightrender()}img.src ./trump.pngimg.complete ? onImgLoaded() : (img.onload onImgLoaded)var imageDatavar x, y, _x, _y, posvar asciiCharArray #KDGLftji;,:..split()var durationPerChar Math.ceil(255 / asciiCharArray.length)function render () {var imageDataContent imageData.datavar part1, part2var lettervar valueasciiCtx.fillStyle #ffffffasciiCtx.fillRect(0, 0, asciiCvsWidth, asciiCvsHeight)asciiCtx.fillStyle #000000asciiCtx.font fontSize px SimHeifor (y 0, _y 0; y offScreenCvsHeight; y samplerStep, _y) {for (x 0, _x 0; x offScreenCvsWidth; x samplerStep, _x) {pos y * offScreenCvsWidth xvalue imageDataContent[pos * 4] * 0.3086 imageDataContent[pos * 4 1] * 0.6094 imageDataContent[pos * 4 2] * 0.0820imageDataContent[pos * 4] imageDataContent[pos * 4 1] imageDataContent[pos * 4 2] valuepart1 Math.floor(value / durationPerChar)part2 value % durationPerCharletter part2 ? asciiCharArray[part1] : (part1 ? asciiCharArray[part1 - 1] : æ)asciiCtx.fillText(letter, _x * fontSize, (_y 1) * fontSize)}}}完美接下来给文字上色......var x, y, _x, _y, posvar r, g, bvar asciiCharArray #KDGLftji;,:..split()......function render () {......for (y 0, _y 0; y offScreenCvsHeight; y samplerStep, _y) {for (x 0, _x 0; x offScreenCvsWidth; x samplerStep, _x) {pos y * offScreenCvsWidth xr imageDataContent[pos * 4]g imageDataContent[pos * 4 1]b imageDataContent[pos * 4 2]value r * 0.3086 g * 0.6094 b * 0.0820imageDataContent[pos * 4] imageDataContent[pos * 4 1] imageDataContent[pos * 4 2] valuepart1 Math.floor(value / durationPerChar)part2 value % durationPerCharletter part2 ? asciiCharArray[part1] : (part1 ? asciiCharArray[part1 - 1] : æ)asciiCtx.fillStyle rgb( r , g , b )asciiCtx.fillText(letter, _x * fontSize, (_y 1) * fontSize)}}}......搞腚核心的完成了下面就简单了只要把资源换成视频然后逐帧截取画面即可html结构如下......您的浏览器不支持 HTML5 video 标签。......js代码如下var video document.getElementById(video)var offScreenCvs document.createElement(canvas)var offScreenCtx offScreenCvs.getContext(2d, { alpha: false })var asciiCvs document.getElementById(ascii-canvas)var asciiCtx asciiCvs.getContext(2d, { alpha: false })var offScreenCvsWidth, offScreenCvsHeight, asciiCvsWidth, asciiCvsHeightvar fontSize 8var samplerStep 4var maxWidth 400, maxHeight 400video.onloadeddata function () {offScreenCvsWidth video.videoWidthoffScreenCvsHeight video.videoHeightvar ratio offScreenCvsWidth / offScreenCvsHeightif (video.videoWidth maxWidth) {offScreenCvsWidth maxWidthoffScreenCvsHeight Math.floor(offScreenCvsWidth / ratio)}if (video.videoHeight maxHeight) {offScreenCvsHeight maxHeightoffScreenCvsWidth Math.floor(offScreenCvsHeight * ratio)}offScreenCvs.width offScreenCvsWidthoffScreenCvs.height offScreenCvsHeightasciiCvsWidth (offScreenCvsWidth / samplerStep 1) * fontSizeasciiCvsHeight (offScreenCvsHeight / samplerStep 1) * fontSizeasciiCvs.width asciiCvsWidthasciiCvs.height asciiCvsHeightoffScreenCtx.drawImage(video, 0, 0, offScreenCvsWidth, offScreenCvsHeight)imageData offScreenCtx.getImageData(0, 0, offScreenCvsWidth, offScreenCvsHeight)render()video.onclick function () {video.paused ? video.play() : video.pause()}video.onplay function () {stop falserendering falserequestAnimationFrame(tick)}video.onpause function () {stop true}}var imageDatavar x, y, _x, _y, posvar r, g, bvar asciiCharArray #KDGLftji;,:..split()var durationPerChar Math.ceil(255 / asciiCharArray.length)function render () {var imageDataContent imageData.datavar part1, part2var lettervar valueasciiCtx.fillStyle #ffffffasciiCtx.fillRect(0, 0, asciiCvsWidth, asciiCvsHeight)asciiCtx.fillStyle #000000asciiCtx.font fontSize px SimHeifor (y 0, _y 0; y offScreenCvsHeight; y samplerStep, _y) {for (x 0, _x 0; x offScreenCvsWidth; x samplerStep, _x) {pos y * offScreenCvsWidth xr imageDataContent[pos * 4]g imageDataContent[pos * 4 1]b imageDataContent[pos * 4 2]value r * 0.3086 g * 0.6094 b * 0.0820imageDataContent[pos * 4] imageDataContent[pos * 4 1] imageDataContent[pos * 4 2] valuepart1 Math.floor(value / durationPerChar)part2 value % durationPerCharletter part2 ? asciiCharArray[part1] : (part1 ? asciiCharArray[part1 - 1] : æ)asciiCtx.fillStyle rgb( r , g , b )asciiCtx.fillText(letter, _x * fontSize, (_y 1) * fontSize)}}}var stop false // 是否停止var timeNow Date.now() // 当前时间戳var timeLast timeNow // 上一帧时间戳var delta 0 // 与上一帧间隔var interval //var fps 60 // 帧率interval 1000 / fps // 每帧耗时var rendering falsevar tick function () {if (stop) return falsetimeNow Date.now()delta timeNow - timeLastif (delta interval) {timeLast timeNowif (!rendering) {rendering trueoffScreenCtx.drawImage(video, 0, 0, offScreenCvsWidth, offScreenCvsHeight)imageData offScreenCtx.getImageData(0, 0, offScreenCvsWidth, offScreenCvsHeight)render()rendering false}}requestAnimationFrame(tick)}除了tick别的基本没变化解释一下这个事实上只要渲染视频并不用这么一长段下面这样即可var tick function () {if (!rendering) {rendering trueoffScreenCtx.drawImage(video, 0, 0, offScreenCvsWidth, offScreenCvsHeight)imageData offScreenCtx.getImageData(0, 0, offScreenCvsWidth, offScreenCvsHeight)render()rendering false}requestAnimationFrame(tick)}多余的这些代码其实可以称为是一段 动画或游戏渲染的范式。因为的requestAnimationFrame渲染频率是根据浏览器的刷新率来的而电脑实时的性能会影响屏幕的刷新率但是通常我们的动画都是固定的帧率为了保持最终渲染出来的帧率尽可能的符合设计所以一般会根据设计的帧率来计算出每一帧的耗时然后根据每一帧的实际耗时来算出理想状态下的变化量以下就是比较常规的设计范式var stop false // 是否停止渲染var timeNow Date.now() // 当前时间戳var timeLast timeNow // 上一帧时间戳var delta 0 // 与上一帧间隔var fps 60 // 帧率var interval 1000 / fps // 每帧耗时var rendering false // 是否渲染某组件var tick function () {if (stop) return falsetimeNow Date.now()delta timeNow - timeLastif (delta interval) {timeLast timeNowif (!rendering) {// loop 代码}}requestAnimationFrame(tick)}教程结束~~~~じゃない那gif怎么搞呢emmmmgif-frames 可以把gif导出多张序列帧后面的原理基本就和视频差不太多了就给大家当课后作业吧 23333Demo4See the Pen ascii_art_pure by Kay (oj8kay) on CodePen.