前端图像处置惩罚(一)

打印 上一主题 下一主题

主题 805|帖子 805|积分 2415

目录

一、上传
1.1、图片转base64
二、图片样式
2.1、图片边框【border-image】
三、Canvas
3.1、把canvas图片上传到服务器
3.2、在canvas中绘制和拖动矩形
3.3、图片(同色区域)点击变色
一、上传

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.         
  47.         // 如果颜色差异大于100或当前像素已经是目标颜色,就跳过
  48.         if (diff(clickColor, curColor) > 100 || diff(curColor, targetColor) === 0) {
  49.             continue;
  50.         }
  51.         
  52.         // 修改颜色
  53.         imgData.set(targetColor, index);
  54.         
  55.         // 对周围的像素点进行处理(上下左右)
  56.         for (const [dx, dy] of directions) {
  57.             const nx = cx + dx, ny = cy + dy;
  58.             
  59.             // 检查边界
  60.             if (nx >= 0 && nx < cvs.width && ny >= 0 && ny < cvs.height) {
  61.                 const key = `${nx},${ny}`;
  62.                 if (!visited.has(key) && diff(clickColor, getColor(nx, ny, imgData)) <= 100) {
  63.                     visited.add(key); // 标记为已访问
  64.                     queue.push([nx, ny]); // 将该像素点加入队列
  65.                 }
  66.             }
  67.         }
  68.     }
  69. }
  70. function diff(color1, color2) {
  71.     return (
  72.         Math.abs(color1[0] - color2[0]) +
  73.         Math.abs(color1[1] - color2[1]) +
  74.         Math.abs(color1[2] - color2[2]) +
  75.         Math.abs(color1[3] - color2[3])
  76.     );
  77. } //计算颜色差异
复制代码
第三个案例总结:
最初使用无穷递归来实现:
  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)逐层处置惩罚:通过 queue.shift() 从队列中取出当前像素点,检查它的上下左右四个方向,并将符合条件的毗邻像素点加入队列。
  (3)避免重复访问:通过 visited 聚集避免重复访问已处置惩罚过的像素点。
  ......待更新

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

农妇山泉一亩田

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表