0


前端图像处理(一)

一、上传

1.1、图片转base64

传统上传:

客户端选择图片,将图片上传到服务端,等服务端返回一个url,客户端再将url设置到图片的src里。图片在渲染时,通过url去请求服务器,服务端就会回馈url,客户端就可以看到预览图了。

优化上传:

客户端选择图片后立刻展示,然后继续上传到服务端保存,他俩互不影响。

了解:

url【统一资源定位符】: 协议://主机名[:端口号]/路径名[?查询字符串][#片段标识符]。

MIME【多用途互联网邮件扩展】: 指示数据的内容类型。

MIME类型内容表示含义文本类型text/html超文本标记语言,用于网页图像类型image/pngPNG 图像格式,支持透明度 音频类型audio/mpegMP3 音频格式 应用类型application/jsonJSON 数据格式,用于数据交换多部分类型multipart/form-data用于 HTTP 表单数据,特别是文件上传
示例:

  1. <body>
  2. <!-- 运行后页面会弹出 alert(123)-->
  3. <script src="data:application/javascript,alert(123)"></script>
  4. </body>

base64:二进制数据转为ASCII 字符串

科普:在js中:

btoa() 函数用于将字符串进行 Base64 编码【btoa('alert(123)')】

atob() 函数用于将 Base64 编码的字符串解码回原始字符串【atob('YWxlcnQoMTIzKQ==')】

进入正题:

  1. <body>
  2. <input type="file" />
  3. <img src="" alt="" id="preview" />
  4. <script src="./1.base64.js"></script>
  5. </body>
  1. const inp = document.querySelector("input");
  2. inp.onchange = function () {
  3. const file = inp.files[0];//多文件,所以是数组
  4. const reader = new FileReader();//创建了一个FileReader 对象,用于读取文件内容
  5. reader.onload = (e) => {
  6. preview.src = e.target.result;// e.target.result 以 Data URL 格式表示,并赋值
  7. console.log(file,'转化后',e.target.result)
  8. };
  9. reader.readAsDataURL(file);//告诉FileReader 以 Data URL 格式读取文件内容
  10. };

后端有时要FormData格式并添加其他参数,而不是原始的二进制格式,可以参考下:

  1. formatImage(type, file) {
  2. if (!this.fileUrl) {
  3. this.$message.warning('请上传图片')
  4. return false
  5. }
  6. for (let i = 0; i < 5; i++) {
  7. const form = new FormData()
  8. form.append('matting_type', i + 1)
  9. form.append('hd_type', i + 1)
  10. form.append('file', file)
  11. waterAxios.post('/oss/upload', form).then((res) => {
  12. if (res.code == 200) {
  13. this.$message.success('上传ok')
  14. }
  15. })
  16. }
  17. },

二进制格式上传的消息格式:application/octet-stream

FormData格式上传的消息格式:multipart/form-data

二、图片样式

2.1、图片边框【border-image】

  1. <style>
  2. body {
  3. background-color: black;
  4. }
  5. .bdr-img {
  6. color: white;
  7. text-align: center;
  8. padding: 5rem;
  9. margin: 2rem auto;
  10. width: 50%;
  11. border: 50px solid #fff;
  12. border-image: url(./stamp.svg) 50 round;
  13. /* 相当于下面三行代码的组合 */
  14. /* border-image-source: url(./stamp.svg);
  15. border-image-slice: 50;
  16. border-image-repeat: round; */
  17. }
  18. </style>
  19. <body>
  20. <div class="bdr-img">
  21. <p>
  22. Hello, My name is [Your Name], and I am a [Your Profession] with [Number
  23. of Years] years of experience in [Your Industry]. I specialize in [Your
  24. Area of Expertise] and have a strong background in [Relevant Skills or
  25. Technologies].
  26. </p>
  27. </div>
  28. </body>

三、Canvas

3.1、把canvas图片上传到服务器

  1. let base64 = canvas.toDataURL()//canvas指canvas格式的图片
  2. let imgUrlBlob = dataURLToBlob(base64)
  3. var file = new window.File([imgUrlBlob], 'image.png', { type: 'image/png' })
  4. let fd = new FormData()
  5. fd.append('image', file)

3.2、在canvas中绘制和拖动矩形

  1. <body>
  2. <div><input type="color" /></div>
  3. <canvas></canvas>
  4. <script src="./canvas.js"></script>
  5. </body>
  1. //============================canvas.js==================
  2. const collorPicker = document.querySelector("input");
  3. const cvs = document.querySelector("canvas");
  4. const ctx = cvs.getContext("2d");
  5. function init() {
  6. const w = 500,
  7. h = 300;
  8. cvs.width = w * devicePixelRatio;
  9. cvs.height = h * devicePixelRatio;
  10. cvs.style.width = w + "px";
  11. cvs.style.height = h + "px";
  12. cvs.style.backgroundColor = "gray";
  13. }
  14. init();
  15. const shapes = [];
  16. // 绘制矩形
  17. // 矩形分为起始坐标和结束坐标,最初结束坐标就是起始坐标,结束坐标随着绘制发生改变
  18. // 告诉canvas左上角是起始坐标,确定最小值和最大值
  19. class Rectangle {
  20. constructor(color, startX, startY) {
  21. this.color = color;
  22. this.startX = startX;
  23. this.startY = startY;
  24. this.endX = startX;
  25. this.endY = startY;
  26. }
  27. //访问器属性
  28. get minX() {
  29. return Math.min(this.startX, this.endX);
  30. }
  31. get minY() {
  32. return Math.min(this.startY, this.endY);
  33. }
  34. get maxX() {
  35. return Math.max(this.startX, this.endX);
  36. }
  37. get maxY() {
  38. return Math.max(this.startY, this.endY);
  39. }
  40. draw() {
  41. ctx.beginPath();
  42. ctx.moveTo(this.minX * devicePixelRatio, this.minY * devicePixelRatio); //左上角(起始坐标)
  43. ctx.lineTo(this.maxX * devicePixelRatio, this.minY * devicePixelRatio); //从左上角到右上角
  44. ctx.lineTo(this.maxX * devicePixelRatio, this.maxY * devicePixelRatio); //从右上角到右下角
  45. ctx.lineTo(this.minX * devicePixelRatio, this.maxY * devicePixelRatio); //从右下角到左下角
  46. ctx.lineTo(this.minX * devicePixelRatio, this.minY * devicePixelRatio); //从左下角到左上角
  47. ctx.fillStyle = this.color;
  48. ctx.fill(); //颜色填充
  49. ctx.strokeStyle = "#fff"; //画笔颜色
  50. ctx.lineCap = "square"; //线条交界处变圆润
  51. ctx.lineWidth = 3 * devicePixelRatio; //画笔宽度
  52. ctx.stroke(); //完成边框的绘制
  53. }
  54. }
  55. // 自己随意画一个矩形
  56. // const rect = new Rectangle("red", 100, 100);
  57. // rect.endX = 200;
  58. // rect.endY = 200;
  59. // rect.draw();
  60. // 鼠标按下确定起始位置,鼠标移动确定结束位置,鼠标抬起结束事件
  61. cvs.onmousedown = (e) => {
  62. const bouding = cvs.getBoundingClientRect();
  63. const rect = new Rectangle(collorPicker.value, e.offsetX, e.offsetY);
  64. // 进行判断
  65. const shape = getShape(e.offsetX, e.offsetY);
  66. if (shape) {
  67. const { startX, startY, endX, endY } = shape;
  68. const moveX = e.offsetX;
  69. const moveY = e.offsetY;
  70. window.onmousemove = (e) => {
  71. //拖动矩形
  72. const disX = e.clientX - bouding.left - moveX;
  73. const disY = e.clientY - bouding.top - moveY;
  74. shape.startX = startX + disX;
  75. shape.startY = startY + disY;
  76. shape.endX = endX + disX;
  77. shape.endY = endY + disY;
  78. };
  79. window.onmouseup = () => {
  80. window.onmousemove = null;
  81. window.onmouseup = null;
  82. };
  83. } else {
  84. shapes.push(rect); //将每个矩形数据加进去
  85. window.onmousemove = (e) => {
  86. rect.endX = e.clientX - bouding.left;
  87. rect.endY = e.clientY - bouding.top;
  88. };
  89. window.onmouseup = () => {
  90. window.onmousemove = null;
  91. window.onmouseup = null;
  92. };
  93. }
  94. };
  95. // 辅助函数:判断鼠标按下时是否落在某个矩形内?是:执行移动 否:执行新建矩形
  96. function getShape(x, y) {
  97. // 从后往前遍历矩形数组,找到最上面的那个矩形
  98. for (let i = shapes.length - 1; i >= 0; i--) {
  99. if (
  100. x >= shapes[i].minX &&
  101. x <= shapes[i].maxX &&
  102. y >= shapes[i].minY &&
  103. y <= shapes[i].maxY
  104. ) {
  105. return shapes[i];
  106. }
  107. }
  108. return null;
  109. }
  110. // 将shapes依次渲染出来
  111. function draw() {
  112. requestAnimationFrame(draw);
  113. ctx.clearRect(0, 0, cvs.width, cvs.height); //画完清空一下
  114. for (const shape of shapes) {
  115. shape.draw();
  116. }
  117. }
  118. draw(); //初始化执行一次,后续在每一帧里执行“画”这个动作,前提:数据shapes已经有了

3.3、图片(同色区域)点击变色

  1. <body>
  2. <canvas></canvas>
  3. <script src="./index.js"></script>
  4. </body>
  1. const cvs = document.querySelector("canvas");
  2. const ctx = cvs.getContext("2d", { willReadFrequently: true }); //获取 Canvas 上下文
  3. function init() {
  4. const img = new Image();
  5. img.onload = () => {
  6. cvs.width = img.width;
  7. cvs.height = img.height;
  8. ctx.drawImage(img, 0, 0, img.width, img.height);
  9. }; //当图片加载完成时:将图片绘制到画布上
  10. img.src = "./redhat.png";
  11. }
  12. init(); //初始化时加载图片
  13. cvs.addEventListener("click", (e) => {
  14. const x = e.offsetX,
  15. y = e.offsetY;
  16. // 1、获取点击位置的颜色: imgData.data就是目标对象所有的颜色信息
  17. const imgData = ctx.getImageData(0, 0, cvs.width, cvs.height); //开始范围,结束范围
  18. const clickColor = getColor(x, y, imgData.data); //点击位置
  19. // 2、改变颜色
  20. const targetColor = [46, 139, 87, 255]; // 改变后颜色为绿色,透明度为不透明
  21. const visited = new Set(); // 记录访问过的像素点
  22. changeColor(x, y, targetColor, imgData.data, clickColor, visited); //点击的像素点改变了
  23. ctx.putImageData(imgData, 0, 0);
  24. });
  25. function pointIndex(x, y) {
  26. return (y * cvs.width + x) * 4;
  27. }
  28. function getColor(x, y, imgData) {
  29. const index = pointIndex(x, y);
  30. return [
  31. imgData[index],
  32. imgData[index + 1],
  33. imgData[index + 2],
  34. imgData[index + 3],
  35. ]; //分别对应:r、g、b、a
  36. }
  37. // 使用BFS来代替递归
  38. function changeColor(x, y, targetColor, imgData, clickColor, visited) {
  39. const queue = [[x, y]]; // 用队列保存待处理的像素点
  40. const directions = [[1, 0], [-1, 0], [0, 1], [0, -1]]; // 上下左右四个方向
  41. visited.add(`${x},${y}`); // 初始像素点标记为已访问
  42. while (queue.length > 0) {
  43. const [cx, cy] = queue.shift(); // 从队列中取出一个像素点
  44. const index = pointIndex(cx, cy);
  45. const curColor = getColor(cx, cy, imgData);
  46. // 如果颜色差异大于100或当前像素已经是目标颜色,就跳过
  47. if (diff(clickColor, curColor) > 100 || diff(curColor, targetColor) === 0) {
  48. continue;
  49. }
  50. // 修改颜色
  51. imgData.set(targetColor, index);
  52. // 对周围的像素点进行处理(上下左右)
  53. for (const [dx, dy] of directions) {
  54. const nx = cx + dx, ny = cy + dy;
  55. // 检查边界
  56. if (nx >= 0 && nx < cvs.width && ny >= 0 && ny < cvs.height) {
  57. const key = `${nx},${ny}`;
  58. if (!visited.has(key) && diff(clickColor, getColor(nx, ny, imgData)) <= 100) {
  59. visited.add(key); // 标记为已访问
  60. queue.push([nx, ny]); // 将该像素点加入队列
  61. }
  62. }
  63. }
  64. }
  65. }
  66. function diff(color1, color2) {
  67. return (
  68. Math.abs(color1[0] - color2[0]) +
  69. Math.abs(color1[1] - color2[1]) +
  70. Math.abs(color1[2] - color2[2]) +
  71. Math.abs(color1[3] - color2[3])
  72. );
  73. } //计算颜色差异

第三个案例总结:

最初使用无穷递归来实现:

  1. // 递归找相同的像素点(上下左右)
  2. changeColor(x + 1, y, targetColor, imgData, clickColor, visited);
  3. changeColor(x - 1, y, targetColor, imgData, clickColor, visited);
  4. changeColor(x, y + 1, targetColor, imgData, clickColor, visited);
  5. changeColor(x, y - 1, targetColor, imgData, clickColor, visited);

但是导致了Maximum call stack size exceeded。最后使用广度优先搜索(BFS)来替代递归:

优势:

(1)使用队列实现BFS:保存待处理的像素点,避免递归带来的栈溢出;

(2)逐层处理:通过

  1. queue.shift()

从队列中取出当前像素点,检查它的上下左右四个方向,并将符合条件的邻接像素点加入队列。

(3)避免重复访问:通过

  1. visited

集合避免重复访问已处理过的像素点。

......待更新

标签: 前端

本文转载自: https://blog.csdn.net/qq_44930306/article/details/143906439
版权归原作者 野槐 所有, 如有侵权,请联系我们删除。

“前端图像处理(一)”的评论:

还没有评论