前言
这么可爱的萌妹子不想拥有一个吗。学起来,自定义一个抱回家
一、前期准备
初始化HTML,利用通配符 *
去除默认样式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="divport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> *,*::before,*::after{ padding: 0; margin: 0; box-sizing: border-box; } </style> </head> <body> <div id="app"></div> </body> <script ></script> </html>
|
二、正文开始
先讲一下实现思路:
- 通过图片上传获取到图片文件,并生成图片地址
- 通过监听图片加载完成事件,将图片画在
canvas1
上
- 获取 canvas1 上的像素点数据,通过压缩,计算灰度,定位等操作,在
canvas2
上画出对应的点
2.1 图片上传
- 我们使用
input
来上传图片,通过 onchange
获取到图片的 file 文件。
- 使用
createObjectURL
生成图片地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="divport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> *,*::before,*::after{ padding: 0; margin: 0; box-sizing: border-box; } </style> </head> <body> <div id="app"> <div><input type="file" accept="image/*" onChange="uploadImage(this)" /></div> <img src="" id="image" style="display: none;"> </div> </body> <script > const image = document.getElementById('image')
function uploadImage(obj){ const newSrc = getObjectURL(obj.files[0]); testImage.src = newSrc; }
function getObjectURL(file) { let url = null ; if (window.createObjectURL!=undefined) { url = window.createObjectURL(file) ; } else if (window.URL!=undefined) { url = window.URL.createObjectURL(file) ; } else if (window.webkitURL!=undefined) { url = window.webkitURL.createObjectURL(file) ; } return url ; }
</script> </html>
|
2.2 画 canvas1
- 监听图片的
onload
事件
- 使用
canvas
的 drawImage
方法将图片画在 canvas1
上
- 通过
canvas
的 getImageData
方法获取 canvas1
的像素点数据
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="divport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> *,*::before,*::after{ padding: 0; margin: 0; box-sizing: border-box; } </style> </head> <body> <div id="app"> <div><input type="file" accept="image/*" onChange="uploadImage(this)" /></div>
<img src="" id="image" style="display: none;"> <canvas id="canvas1"></canvas> </div> </body> <script > const image = document.getElementById('image') const canvas1 = document.getElementById('canvas1')
image.onload = ()=>{ const ctx1 = canvas1.getContext('2d')
const { width, height } = image
canvas1.width = width canvas1.height = height
ctx1.drawImage(image,0, 0, width,height ) const imageData = ctx1.getImageData( 0, 0, width, height).data }
</script> </html>
|
2.3 画 canvas2
在获取到 canvas1
的像素点数据后,通过压缩,计算灰度,定位,再画在 canvas2
上。先上代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60
| <div id="app"> <div><input type="file" accept="image/*" onChange="uploadImage(this)" /></div>
<img src="" id="image" style="display: none;"> <canvas id="canvas1"></canvas> <canvas id="canvas2"></canvas> </div>
const image = document.getElementById('image') const canvas1 = document.getElementById('canvas1') const canvas2 = document.getElementById('canvas2')
image.onload = ()=>{ const ctx1 = canvas1.getContext('2d') const ctx2 = canvas2.getContext('2d')
const { width, height } = image
canvas1.width = width canvas1.height = height
canvas2.width = width canvas2.height = height
ctx1.drawImage(image, 0, 0, width, height)
const imageData = ctx1.getImageData( 0, 0, width, height).data
for(let i = 0; i < imageData.length; i += 4){
const x = parseInt(i % (width * 4) / 4) const y = parseInt(i / (width * 4))
const bl = 6
if(x % bl === 0 && y % bl === 0 ){ const [r, g, b, a] = [imageData[i], imageData[i + 1], imageData[i + 2], imageData[i + 3]] const gray = (r + g + b) / 3
ctx2.font = '12px' ctx2.fillStyle = `rgba(${gray},${gray},${gray},${a})` ctx2.fillText('a', x, y) } } }
|
2.3.1 压缩
- 为啥要压缩? 如图所示,右边是我们要现实的最终效果,是可以清晰看出图片是由深浅不一的字母 a 组成的(如果觉得不够直观,图片右击,在新标签页中打开图片)。不进行压缩的话,即每个像素点都使用 a 来展示的话,字母 a 会因为太小而无法直观看出,如图左边部分效果。
- 如何压缩? 如图所示,假设红色框为一整张图片,每一个小灰格子为一个像素点,蓝色格子为四个像素点组成的。我们将蓝色格子的左上角的像素点的颜色作为这个蓝色格子颜色,这样我们就把四个像素点压缩成一个。
2.3.2 计算灰度
- 计算灰度前,我们先认识一下通过
canvas
的 getImageData
获取到的像素点数据。
`imageData` 为像素点颜色的RGBA色值数组构成,即[R,G,B,A,R,G,B,A,R,G,B,A]。数组每四个值代表一个像素点的颜色。
\2. 计算灰度
一个像素点的灰度值即为 gray=(R+G+B)/3
。该像素点的灰度色值即为rgba(gray,gray,gray,A)
2.3.3 定位
在认识像素点数据的构成后,我们得知所有像素点的色值都放在同一个数据里,这样无法判断像素点在图片上的位置,因此需要通过计算来定位。
- 计算每个像素点的位置 (x , y)
width
为一整行的像素点的个数。因为一个像素点由四个色值组成,所以一整行所占的数组长度为width*4
。
1 2 3 4 5 6 7 8
| const imageData = ctx1.getImageData( 0, 0, width, height).data
for(let i = 0; i < imageData.length; i += 4){ const x = parseInt(i % (width * 4) / 4) const y = parseInt(i / (width * 4)) }
|
- 根据前面压缩的算法,以左上角第一个像素点的颜色计算压缩后的颜色和位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| const imageData = ctx1.getImageData( 0, 0, width, height).data
const bl = 6
for(let i = 0; i < imageData.length; i += 4){ const x = parseInt(i % (width * 4) / 4) const y = parseInt(i / (width * 4)) if(x % bl === 0 && y % bl === 0 ){ } }
|
2.3.4 在canvas2上画点
1 2 3 4 5
| ctx2.font = '12px' ctx2.fillStyle = `rgba(${gray},${gray},${gray},${a})` ctx2.fillText('a', x, y) 复制代码
|
三、完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
| <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="divport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <style> *,*::before,*::after{ padding: 0; margin: 0; box-sizing: border-box; } </style> </head> <body> <div id="app"> <div><input type="file" accept="image/*" onChange="uploadImage(this)" /></div>
<img src="" id="image" style="display: none;"> <canvas id="canvas1"></canvas> <canvas id="canvas2"></canvas> </div> </body> <script > const image = document.getElementById('image') const canvas1 = document.getElementById('canvas1') const canvas2 = document.getElementById('canvas2')
function uploadImage(obj){ const newSrc = getObjectURL(obj.files[0]); image.src = newSrc; }
function getObjectURL(file) { let url = null ; if (window.createObjectURL!=undefined) { url = window.createObjectURL(file) ; } else if (window.URL!=undefined) { url = window.URL.createObjectURL(file) ; } else if (window.webkitURL!=undefined) { url = window.webkitURL.createObjectURL(file) ; } return url ; }
function computedSize(size){ const maxWidth = 600
let { width, height } = size
width > maxWidth && (height = height * maxWidth / width) width > maxWidth && (width = maxWidth)
return { width, height } }
image.onload = ()=>{ const ctx1 = canvas1.getContext('2d') const ctx2 = canvas2.getContext('2d')
const { width, height } = computedSize(image)
canvas1.width = width canvas1.height = height
canvas2.width = width canvas2.height = height
ctx1.drawImage(image, 0, 0, width, height)
const imageData = ctx1.getImageData( 0, 0, width, height).data
for(let i = 0; i < imageData.length; i += 4){
const x = parseInt(i % (width * 4) / 4) const y = parseInt(i / (width * 4))
const bl = 6
if(x % bl === 0 && y % bl === 0 ){ const [r, g, b, a] = [imageData[i], imageData[i + 1], imageData[i + 2], imageData[i + 3]] const gray = (r + g + b) / 3
ctx2.font = '12px' ctx2.fillStyle = `rgba(${gray},${gray},${gray},${a})` ctx2.fillText('a', x, y) } } }
</script> </html>
|
四、升级版
通过文字 ,图片,视频 生成图像
[文字效果]: http://wuxingxi.top/text-image/demo1.html “ “
视频效果大致思路:将img
标签换成video
标签,播放视频时,利用 requestAnimationFrame
循环将视频的每一帧通过 ctx1.drawImage(video,0, 0, width,height)
画在 canvas
上